In the last blog post in this series, we had a look at Fable, the F# wrapper around JavaScript. We also briefly hinted towards the many F# libraries that provide access to JavaScript libraries. Today we'll focus on Feliz, an F# wrapper around React that allows us to write interactive front end applications with ease.
React
React is a hugely popular JavaScript UI framework that allows you to build UIs without having to worry too much about state management. It has a strong focus on composability: you build small UI components, that you can compose and reuse in larger applications.
Let's have a look at a very basic example of one of those components, a Counter app:
import React from 'react';
export function App(props) {
const [counter, setCounter] = React.useState(0)
return (
<div className='App'>
<button onClick = {() => setCounter( counter + 1 )}>Increment</button>
<p>Value: {counter}</p>
</div>
);
}
Use this online editor if you want to play around with this example.
React uses functions to create isolated components, in our case the App
function. Our component has a bit of internal state, which you manage using the useState
function. When you give this function a default value, it will return 2 values: the current state, and a function to set the state. We display the returned value in our application, but we can use it as any old value, perhaps passing in into another element.
Feliz
The code above is a fairly elegant piece of JavaScript, but for reasons explained in the previous post of this series, we'd rather use F#. Fortunately, there's a great React wrapper called Feliz. As a wrapper, Feliz, stays very close to raw React, and we can easily reproduce our counter:
[<ReactComponent>]
let Counter =
let count, setCount = React.useState(0)
Html.div [
Html.button [
prop.onClick (fun _ -> setCount(count + 1))
prop.text "Increment"
]
Html.p [
prop.text $"Value: {count}"
]
]
Great! React knowledge is directly applicable to Feliz and vice versa!
If you want a very quick demo of the advantages of using F# here, try changing React.useState(0)
to React.useState("0")
. The F# example will fail with a compiler error, becasue we add an integer to a string. In JavaScript, everything seems fine until you pressed the button: because of string concatenation, the counter will end up having a value like "0111"
.
DSL
You may have noticed that the most noticeable difference between these two examples is the way we build up the HTML. React makes use of JSX, a language closely related to HTML to allow you to embed elements in your code. Feliz uses a more F#-native approach using lists. Let's have a closer look at this DSL.
In order to expose all properties that can be set on a component, we can treat the component (which we can think of as an IReactElement
) as a function of IReactProperty
list.
IReactProperty list -> IReactElement
You can set the children using prop.children
. If we use this function, the div
in the example above would become:
Html.div [
prop.children [
Html.button [...]
Html.p [...]
]
]
But the function also has a second overload, taking the children directly:
IReactElement list -> IReactElement
Which leads to this more concise notation:
Html.div [
Html.button [...]
Html.p [...]
]
The fact that the dsl works with F# lists means that we can simply use all functions from the List
module to introduce more complex logic, including comprehensions, for example when conditionally rendering elements, or when mapping domain data to elements:
type LoginError =
| InvalidUsername
| InvalidPassword
| NotRegistered
let errorMessage =
function
| InvalidUsername -> "The provided username is invalid"
| InvalidPassword -> "The provided password is invalid"
| NotRegistered -> "There is no user registered with this username"
let errors = [ InvalidPassword; InvalidPassword ]
let errorList =
Html.ul [
for error in errors do
Html.li [ prop.text (errorMessage e) ]
]
Elmish
Now we have way to build UIs with a reasonably simple way of using state. As things grow, having the actions that manipulate the state scattered through the elements can become a bit hard to follow. Elmish provides a solution to this by introducing a separation between the UI and the model using messages. The UI dispatches messages that mutate the model, and the model is fed back into the UI to display the information. Let's see what this looks like for our counter example:
We define a model, and a function that provides it's initial state:
type Model = { Count: int }
let init () = { Count = 0 }, Cmd.none
Messages that can manipulate that model, and a function that knows how to deal with these messages:
type Message =
| Increment
let update message model =
match message with
| Increment -> { model with Count = model.Count + 1 }, Cmd.none
We also have a function that creates the user interface based on the current state and can dispatch messages back into the loop:
let ElmishCounter state dispatch =
Html.div [
Html.button [
prop.onClick (fun _ -> Increment |> dispatch)
prop.text "Increment"
]
Html.p [
prop.text $"Value: {state.Count}"
]
]
There's also some wiring up to be done, but we won't cover that here since the SAFE template has this all set up for you!
If you want to use Elmish for a smaller section of an application (for example an Elmish loop per page), check out the UseElmish hook.
As you can see, using F# and React make a very good stack for front end development. You can build applications directly on top of React using Feliz, but if you prefer a stonger separation between behaviour and UI, Elmish got your back.