Fable coupled with Fable.React and Fable.Elmish.React are powerful tools to generate javascript applications. But generating good and fast React code is an already complex task that isn’t made simpler by using a different language and a transpiler.

In this series of posts I plan to show how some common React constructs are done in F# and what to look for when optimizing them.

The posts will be:

  1. React in Fable land (This one)
  2. Optimizing React
  3. Applying to Elmish

Starting a sample Fable React project

If you want to try the code for yourself you’ll need a sample

  • Start with the fable template as in the minimal template
  • Run it in watch mode with npm start and connect to it via http://localhost:8080/
  • Replace the div tag in public/index.html with <div id="root"></div>
  • Change the App.fs file to look like that:
module App

open Fable.Core
open Fable.Core.JsInterop
open Fable.Import
open Fable.Import.Browser
open Fable.Import.React
open Fable.Helpers.React
open Fable.Helpers.React.Props

// -- [BEGIN] part to replace
let init() =
    let element = str "Hello 🌍"
    ReactDom.render(element, document.getElementById("root"))
// -- [END] part to replace

init()

Hello 🌍

Creating HTML elements

As F# doesn’t have any JSX-like transform creating React elements is done as explained in the React Without JSX article, except that instead of directly using createElement a bunch of helpers are available in the Fable.Helpers.React module.

For HTML elements the resulting syntax is strongly inspired by the Elm one.

Here is a small sample of the more common ones :

let init() =
    let element =
        // Each HTML element has an helper with the same name
        ul
            // The first parameter is the properties of the elements.
            // For html elements they are specified as a list and for custom
            // elements it's more typical to find a record creation
            [ClassName "my-ul"; Id "unique-ul"]

            // The second parameter is the list of children
            [
                // str is the helper for exposing a string to React as an element
                li [] [ str "Hello 🌍" ]

                // Helpers exists also for other primitive types
                li [] [ str "The answer is: "; ofInt 42 ]
                li [] [ str "π="; ofFloat 3.14 ]

                // ofOption can be used to return either null or something
                li [] [ str "🤐"; ofOption (Some (str "🔫")) ]
                // And it can also be used to unconditionally return null, rendering nothing
                li [] [ str "😃"; ofOption None ]

                // ofList allows to expose a list to react, as with any list of elements
                // in React each need an unique and stable key
                [1;2;3]
                    |> List.map(fun i ->
                        let si = i.ToString()
                        li [Key si] [str "🎯 "; str si])
                    |> ofList

                // fragment is the <Fragment/> element introduced in React 16 to return
                // multiple elements
                [1;2;3]
                    |> List.map(fun i ->
                        let si = i.ToString()
                        li [] [str "🎲 "; str si])
                    |> fragment []
            ]
    ReactDom.render(element, document.getElementById("root"))

Output of helpers demonstration

React components

While it is possible to use React as a templating engine for HTML by using only built-in components, what really unlocks the power of React and where lies the biggest potential for optimization is in its user-defined components.

Creating Components in F# is really similar to how they are created in modern JavaScript. The main difference comes when consuming them as we’ll use the ofType and ofFunction helpers (Instead of using JSX or React.createElement).

Functional Components

The easiest to use F# components are Functional ones, they don’t need a class, a simple function taking props and returning a ReactElement will do. They can then be created using the ofType helper.

Let’s see how they are created in JavaScript:

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

function init() {
    const element = <Welcome name="🌍" />;
    ReactDOM.render(element, document.getElementById("root"));
}

And the equivalent in F#:

type WelcomeProps = { name: string }

let Welcome { name = name } =
    h1 [] [ str "Hello, "; str name ]

let inline welcome name = ofFunction Welcome { name = name } []

let init() =
    let element = welcome "🌍"
    ReactDom.render(element, document.getElementById("root"))

Hello, 🌍

Some notes:

  • We had to declare WelcomeProps while JavaScript could do without
  • Using sprintf in the F# sample could have seemed natural but using React for it is a lot better on a performance standpoint as we’ll see later.

Note: Due to some peculiarities of the Fable transform there can be negative performance impact of using them but they are avoidable if you know what to look for. I’ll detail this some more in the second post

Class Components

To create an user-defined component in F# a class must be created that inherit from Fable.React.Component<'props,'state> and implement at least the mandatory render() method that returns a ReactElement.

Let’s port our “Hello World” Component:

type WelcomeProps = { name: string }

type Welcome(initialProps) =
    inherit Component<WelcomeProps, obj>(initialProps)
    override this.render() =
        h1 [] [ str "Hello "; str this.props.name ]

let inline welcome name = ofType<Welcome,_,_> { name = name } []

let init() =
    let element = welcome "🌍"
    ReactDom.render(element, document.getElementById("root"))

Hello, 🌍

Nothing special here, the only gotcha is that the props passed in the primary constructor even though they are in scope in the render() method should not be used.

Class Component with state

All features of React are available in Fable and while the more “Functional” approach of re-rendering with new props is more natural using mutable state is totally possible :

// A pure, stateless component that will simply display the counter
type CounterDisplayProps = { counter: int }

type CounterDisplay(initialProps) =
    inherit PureStatelessComponent<CounterDisplayProps>(initialProps)
    override this.render() =
        div [] [ str "Counter = "; ofInt this.props.counter ]

let inline counterDisplay p = ofType<CounterDisplay,_,_> p []

// Another pure component displaying the buttons
type AddRemoveProps = { add: MouseEvent -> unit; remove: MouseEvent -> unit }

type AddRemove(initialProps) =
    inherit PureStatelessComponent<AddRemoveProps>(initialProps)
    override this.render() =
        div [] [
            button [OnClick this.props.add] [str "👍"]
            button [OnClick this.props.remove] [str "👎"]
        ]

let inline addRemove props = ofType<AddRemove,_,_> props []

// The counter itself using state to keep the count
type CounterState = { counter: int }

type Counter(initialProps) as this =
    inherit Component<obj, CounterState>(initialProps)
    do
        this.setInitState({ counter = 0})

    // This is the equivalent of doing `this.add = this.add.bind(this)`
    // in javascript (Except for the fact that we can't reuse the name)
    let add = this.Add
    let remove = this.Remove

    member this.Add(_:MouseEvent) =
        this.setState({ counter = this.state.counter + 1 })

    member this.Remove(_:MouseEvent) =
        this.setState({ counter = this.state.counter - 1 })

    override this.render() =
        div [] [
            counterDisplay { CounterDisplayProps.counter = this.state.counter }
            addRemove { add = add; remove = remove }
        ]

let inline counter props = ofType<Counter,_,_> props []

// createEmpty is used to emit '{}' in javascript, an empty object
let init() =
    let element = counter createEmpty
    ReactDom.render(element, document.getElementById("root"))

Counter = 42

Note: This sample uses a few react-friendly optimizations that will be the subject of the second post.

That’s all folks

Nothing special this time and for anyone that know both React and Fable there was not a lot of new information but we’ll expand it next time !