Skip to main content
Version: 2.9.0

Stateful Components

Components can be tremendously useful when they manage their internal state. This allows for some pieces of the UI to have stateful bevavior without polluting the global Elmish state. For example when you have an accordion component, whether it is collapsed or expanded is a piece of information that might not be relevant to your Elmish models so you keep that information internal to that specific component. Otherwise, if you are "going Elmish all the way", you would need to keep track of which accordion is now in it's open state and have messages that are resposible for expanding and collapsing a specific accordion.

State with React components is incorporated using React Hooks which allow you to define state variables inside your functional components. A common hook to use with React is the React.useState hook.

It is recommended that you use hooks only inside the defintion of your [<ReactComponent>] such that you could distinguish which parts of your application are simple functions and which parts are (stateful) components.

0

Show code
module Example.React

open Fable.Core
open Feliz

[<Erase; Mangle(false)>]
type Counter =

[<ReactComponent(true)>]
static member Counter() =

let (count, setCount) = React.useState 0

Html.div [
Html.h1 count
Html.button [
prop.text "Increment"
prop.onClick (fun _ -> setCount(count + 1))
]
]
[<ReactComponent>]
let Stateful() =
let (count, setCount) = React.useState(0)
Html.div [
Html.h1 count
Html.button [
prop.text "Increment"
prop.onClick (fun _ -> setCount(count + 1))
]
]

The hook React.useState is one of the most useful hooks which allows you to define a state variable. The syntax of React.useState follows:

let (value, setValue) = React.useState (initialValue)

where

val initialValue : 'T
val value : 'T
val setValue : 'T -> unit

The setValue function is what is called a setter function which is why it is common to prefix the function with "set[Something]".

When you call that function, the value will be updated and the component re-renders using this newly updated value.

Multiple State Variables

A single component can keep track of multiple state variables at the same time. Here is an example

0

Show code
module Example.MultipleStateVariables

open Feliz

[<ReactComponent(true)>]
let MultipleVariables() =
let (count, setCount) = React.useState(0)
let (textColor, setTextColor) = React.useState(color.red)

Html.div [
Html.h1 [
prop.style [ style.color textColor ]
prop.text count
]

Html.button [
prop.text "Increment"
prop.onClick (fun _ -> setCount(count + 1))
]

Html.button [
prop.text "Red"
prop.onClick (fun _ -> setTextColor(color.red))
]

Html.button [
prop.text "Blue"
prop.onClick (fun _ -> setTextColor(color.blue))
]
]

Lazy State

React will call the initialization on every rerender, even if it's no longer needed. In most cases this has no performance impact at all, however, in some cases where the initial value is very expensive this can cause an application to become unusable.

You can ensure that expensive initialization functions only run once by passing in an initializer function into React.useState:

[<ReactComponent>]
let UseStateNormal() =
let count,setCount = React.useState (sortNumbers())
Html.div [
prop.classes [ Bulma.Box ]
prop.children [
Html.div [
prop.text (sprintf "Normal Count: %i" count)
]
]
]

[<ReactComponent>]
let UseStateLazy() =
let count,setCount = React.useState (fun () -> sortNumbers())
Html.div [
prop.classes [ Bulma.Box ]
prop.children [
Html.div [
prop.text (sprintf "Lazy Count: %i" count)
]
]
]

Simplified Model-View-Update components with React.useReducer

You can use the built-in React.useReducer hook to build simple model-view-update style components inside your application:

0

Show code
module Example.UseReducer

open Feliz

type State = { Count : int }

type Msg = Increment | Decrement

let initialState = { Count = 0 }

let update (state: State) = function
| Increment -> { state with Count = state.Count + 1 }
| Decrement -> { state with Count = state.Count - 1 }

[<ReactComponent(true)>]
let Counter() =
let (state, dispatch) = React.useReducer(update, initialState)
Html.div [
Html.button [
prop.onClick (fun _ -> dispatch Increment)
prop.text "Increment"
]

Html.button [
prop.onClick (fun _ -> dispatch Decrement)
prop.text "Decrement"
]

Html.h1 state.Count
]
type State = { Count : int }

type Msg = Increment | Decrement

let initialState = { Count = 0 }

let update (state: State) = function
| Increment -> { state with Count = state.Count + 1 }
| Decrement -> { state with Count = state.Count - 1 }

[<ReactComponent>]
let Counter() =
let (state, dispatch) = React.useReducer(update, initialState)
Html.div [
Html.button [
prop.onClick (fun _ -> dispatch Increment)
prop.text "Increment"
]

Html.button [
prop.onClick (fun _ -> dispatch Decrement)
prop.text "Decrement"
]

Html.h1 state.Count
]