Skip to content

Latest commit

 

History

History
129 lines (93 loc) · 5.03 KB

File metadata and controls

129 lines (93 loc) · 5.03 KB
title Feliz.UseElmish
sidebar_position 9

Feliz.UseElmish Nuget

import ComponentRender from '@site/src/components/ComponentRender'; import CodeBlock from '@theme/CodeBlock';

Besides being able to use Feliz in existing Elmish applications, you can also use Elmish as part of your Feliz application. This is a different approach to building standalone React components that use Elmish internally to manage the state of the component but from the perspective of the consumer, it is just another React component.

This approach simplifies the original Elmish model where the application state is explicitly passed down in parts to children and events are passed up to the parent components.

The implementation of this approach is made possible using a React hook called React.useElmish. The following examples demonstrate how to use it:

Install into your project

dotnet add package Feliz.UseElmish

or

dotnet femto install Feliz.UseElmish

:::danger Feliz.UseElmish does not support Server-Side Rendering (SSR). Help me out by contributing a PR if you need this feature. :::

Here is an example to demonstrate how to build such component:

import RawElmishCounter from '!!raw-loader!../../feliz-docs/Examples/React/ElmishCounter.fs'

{RawElmishCounter}

The difference here from a full-fledged Elmish applications is that there isn't an "Elmish entry point" to run the component and manage its life-cycle. Instead, the React.useElmish hooks manages the Elmish life-cycle internally within the React component so that it can run standalone inside other React components:

[<ReactComponent>]
let Counters() =
    Html.div [
        Counter()
        Counter()
        Counter()
    ]

When you need to trigger events from such an Elmish component, use React patterns where you pass a callback via the props instead of passing the dispatch function from the parent component.

Understading the dependencies array

It is also important to understand the dependencies array of the React.useElmish function

//                                             dependencies array
//                                                    |
//                                                    |
//                                                    ↓
let state, dispatch = React.useElmish(init, update, [| |])

This array is responsible for the re-initialization of the component. For example, if your mini Elmish component loads user profile based on an input user ID like this:

[<ReactComponent>]
let UserProfile(userId: int) =
    // will initialize once even if the component is re-rendered using a different userId
    let state, dispatch = React.useElmish(init userId, update, [| |])
    renderUserProfile state disptch

Then you must add the userId to the dependencies array so that the hook knows to call init again and re-initialize the component:

[<ReactComponent>]
let UserProfile(userId: int) =
    //                                               inititialization dependency
    //                                                              |
    //                                                              |
    //                                                              |
    // now every time this component is rendered using              |
    // a different userId, it will reinitialize the component       ↓
    let state, dispatch = React.useElmish(init userId, update, [| box userId |])
    renderUserProfile state disptch

// Here, we are using a router so that every time the URL changes
// say from /user/20 to /user/21 then the UserProfile will be reload that user
open Feliz.Router

[<ReactComponent>]
let App() =
    let currentUrl, updateCurrentUrl = React.useState(Router.currentUrl())
    React.router [
        router.onUrlChanged updateCurrentUrl
        router.children [
            match currentUrl with
            | [ "user"; Route.Int userId ] -> UserProfile(userId)
            | _ -> Html.h1 "Not found"
        ]
    ]

open Browser.Dom

ReactDOM.render(App(), document.getElementById "feliz-app")

The dependencies array is compared using F# structural equality (= / <>). React.useElmish re-initializes when the dependencies become structurally different.

This differs from React.useEffect, which compares dependency entries using Object.is.

Combining with other hooks

Next, let's combine this hook with other React hooks such as React.useState and React.useEffect:

import RawElmishCounterSubscription from '!!raw-loader!../../feliz-docs/Examples/React/ElmishCounterSubscription.fs'

{RawElmishCounterSubscription}

Disposing of resources

Documentation/Samples WIP

TL;DR: Have your State/Model type implement IDisposable and React.useElmish will take care of calling the dispose function when the component unmounts.