Feliz React components

I have been looking for a best practice to create a major single page application using the excellent Feliz library. I also heavily borrowed ideas from a book by the author of Feliz, Zaid Ajaj. The application in mind is intended to be the Dutch National Pediatric Emergency app used for acute interventions in pediatric medical emergencies. The main purpose of the application is to provide all calculations necessary based on age and weight of the patient.

One of the big challenges in creating a user interface is to control the complexity. User interfaces are complex as they are used by humans. And human behavior is complex and unpredictable. A way to control this complexity is the Elm architecture.

Diagram of The Elm Architecture
The Elm architecture

There are the following main parts:

  • The Model
  • The View
  • The Update

However, in a real life application there are a lot of different UI components involved and they all have to communicate with each other. So, in effect there can be a lot of Model-View-Update programs, if one considers a program as a modular structure consisting of a lot of smaller programs or components.

Also, in a large application the “Model” can be polluted by other data not relevant to the real relevant model. For example, as in the above figure the color of the minus and plus buttons can be changed and have to be retained, this also becomes part of the “Model”.

So, maybe its better to distinguish between a Model (i.e. the relevant business data being represented) and the State, i.e. the state the UI is in. This would also fit nicer with an architecture were a UI is build from smaller components, each managing its own state but contributing in the larger picture to the relevant model.

The current UI that I am building looks like:

Emergency app UI

As you can see there are couple of Select components to select a year, month, week and/or day to determine the age of the patient. Also, weight and height can be, thus, selected.

The code for the select item looks like this.


module Select =

    open Elmish
    open Feliz
    open Feliz.UseElmish
    open Feliz.MaterialUI


    type State<'a> = { Selected: 'a Option }


    let init value : State<_> * Cmd<_> =
        {
            Selected = value
        },
        Cmd.none


    type Msg<'a> = Select of 'a

    // the update function is intitiated with a handle to
    // report back to the parent which item is selected.
    let update handleSelect msg state =
        match msg with
        | Select s ->
            { state with Selected = s |> Some },
            Cmd.ofSub (fun _ -> s |> handleSelect)


    let useStyles =
        Styles.makeStyles (fun styles theme ->
            {|
                formControl =
                    styles.create [
                        style.minWidth "115px"
                        style.margin 10
                    ]
            |}
        )

    
    [<ReactComponent>]
    let View
        (input: {| label: ReactElement
                   items: 'a list
                   value: 'a option
                   handleSelect: 'a -> unit |})
        =
        let classes = useStyles ()

        let state, dispatch =
            React.useElmish (
                init input.value,
                update input.handleSelect,
                [| box input.value |]
            )

        Mui.formControl [
            prop.className classes.formControl
            formControl.margin.dense
            formControl.children [
                Mui.inputLabel [ input.label ]
                Mui.select [
                    state.Selected
                    |> Option.map string
                    |> Option.defaultValue ""
                    |> select.value

                    input.items
                    |> List.mapi (fun i item ->
                        let s = item |> string

                        Mui.menuItem [
                            prop.key i
                            prop.value s
                            prop.onClick(fun _ ->
                                item
                                |> Select
                                |> dispatch
                            )
                            menuItem.children [
                                Mui.typography [
                                    typography.variant.h6
                                    prop.text s
                                ]
                            ]
                        ]
                    )
                    |> select.children
                ]
            ]
        ]

    // render the select with a label (a ReactElement)
    // the items to select from, a value that should be
    // selected (None of no value is selected) and 
    // a handle that gives back the selected element.
    let render label items value handleSelect =
        View(
            {|
                label = label
                items = items
                value = value
                handleSelect = handleSelect
            |}
        )

The code shown above creates a ReactComponent which in itself functions as a stand alone Elm program. It has its own State-View-Update cycle. The only way to report back to the parent initiating the component is through the handleSelect function.

The parent can then render a select component like:

type Msg = YearChange of int

let items = [1..18]
let label = Mui.typography [ prop.text "Year" ]
let value = None
let handleSelect = YearChange >> dispatch

Select.render label items value handelSelect

So, there is a parent Msg type with a constructor YearChange. And the handleSelect is a created by composing the YearChange constructor with the dispatch function of the parent, essentially turning into a function int -> unit.

Leave a Reply

Your email address will not be published. Required fields are marked *