My tips for working with Elmish
This article is intended for people who are already familiar with the MVU architecture. You can learn more about it on the Elmish website.
I am one of the maintainer of Elmish and I created several Fable / Elmish libraries and applications. When doing so, I needed to understand Elmish deeply and I want to take the opportunity of F# Advent Calendar to share with you my tips and knowledge.
I will be covering a lot in this article, so grab yourself a cup of tea 🍵, take a deep breath 💨 and let’s get started 🏁.
A command is a container for a function that Elmish executes immediately but, may schedule dispatch of message at any time.
- Issue immediately a new Message
- Make an HTTP call and then return the result in your application via a Message
- Save data in your storage
In this section, I will cover all the default functions offered by Elmish and show an example of it.
Cmd.none to schedule no Commands.
Cmd.ofMsg to schedules another message directly, it can be seen as a way to chain messages.
Cmd.map to map a command to another type. In general, you use this function when working with nested components.
Cmd.ofAsync to evaluate an
async block and map the result into success or error (of exception).
Cmd.ofFunc to evaluate a simple function and map the result into success or error (of exception).
Cmd.performFunc to evaluate a simple function and map the success to a message discarding any possible error.
Cmd.attemptFunc to evaluate a simple function and map the error (in case of exception)
Cmd.ofPromise to call
promise block and map the results.
Cmd.ofSub to call the subscriber. This is useful when you are dealing with an API which use callbacks or to listen to an event.
Cmd.batch to aggregate multiple commands. You can aggregate any of the previous commands together.
The functions provided out of the box by Elmish are enough to cover most of the needs of your application. However, when working on a library or with a specific API you could want to create your own commands. We will take a look at it later.
Modelize your Model according to your needs
When working with a Single Page Application, you will have to deal with navigation and need to reflect it in your models.
In general, when I am working with at a page level, I use Discrimination Union so I can store only the active page state.
For example, if in your application you have a login page you can’t store your authenticated page state in your application because you can’t fetch the resource to display it.
In general my main
Model looks like this:
Using DUs to represent your pages state helps you isolate your logics. For me, the biggest benefit compared to storing all page states in a big record, is that you are not caching your page when navigating.
Of course sometimes your application needs to maintain state between navigation. For example, if you have a form in several steps you could modelize it like this:
Everything is a function
One thing we tend to forget when working with Elmish is that everything is a function. The main reason for this forgetfulness is that most of the examples only show you this code:
So in our mind
update takes two arguments when in fact we should think
update takes at least two arguments. The same applies to
This idea is linked to the fact that a component’s
Model should have all the information needed by the component, but this is not always true.
Pass data as an argument
For example, if you have a session in your application in order to make HTTP calls, should you store this session in each component’s
No, you should store it at one place and then pass your session to the functions that need it.
The next code illustrates this situation, we request a
session argument in our init function because we need to make an authenticated http request using
Http.Auth.* (custom module used, includes the session info in a request).
Pass a record as an argument
If your view function takes several arguments you can use a record as an argument. It will force you to name the arguments and by doing so make your code easier to read and maintain over time.
It will also make it easier to optimize your code for react using
Use helper function to work with your Domain
For example, when an elmish component needs to fetch data from the server often, I like to show a loading animation. Here is how I handle it:
As you can see I use a DU to represent if the component is
Loading or in an
Errored state. And in order to simplify my update function, I use the
applyIfLoaded function. This helps me keep the update code simple and easy to read.
Make public bare minimum
By default in F# everything is public, so you need to explicitly use
private keyword for your functions and types to restrict their access.
As you can see, I am only exposing
view functions to the parent. This will allow easier refactoring and easier usability for you or others because you can directly see what API is available.
Make the child communicate with the parent
In general, a child should be independent from its parent and doesn’t send messages to it. However, this is sometimes needed, for example if you have a
Login component you want to be able to detect a successful login and save the session in your application.
Most people do it this way:
failwith in the child to make “sure” to remember to capture the
LoginCompleted message in the parent as it will throw an exception if we don’t capture it.
However, there is a major problem, what if you add another message to capture in
Login.Msg then the compiler will not be able to help us because we are using a greedy pattern
| loginMsg -> .
A good solution to this problem is to use what I call
Model * Cmd<Msg> * ExternalMsg we help the compiler guide us to the correct way of handling the data. It forces me to handle
ExternalMsg in the parent. Also, if I add a new case it will generate a warning telling me where I need to handle the new case.
Schedule a parent message in a child component
When working on a library or abstraction layer it can be needed to schedule a message coming from a parent in the child components.
For example Thoth.Elmish.Debouncer (we will call it debouncer from now), is using this feature in order to debounce a message.
In order to do that the debouncer exposes two functions:
bouncewhich is working with
updatewhich is working with
If you want to see how this is implemented, you can take a look at the code it’s only about 50 lines.
Thoth is a set of several libraries for working with Fable applications. - MangelMaxime/Thoth
Use several views for the same component
Sometimes a component has the same features but different displays, but you still want to use it in several places in your application.
For example, in the next code we render the
Switch.switch element in a different size depending of ours needs.
Extends the Elmish API
In this section, I will show you advanced Elmish usage. Before using it in your application or library, you need to weight the pros and cons compare to not using the standard features set.
For example, if
Cmd.ofSub works, you should always favor using
Cmd.ofSub over creating a custom command.
Also, this is not intended to be a guide/tutorial on how to implement it, I just want to mention that this is possible.
By using program composition, you can add a new behavior to any Elmish application for example you can make it support navigation via URL, render using React, attach a debugger, etc.
This is exactly what you do when wiring your Elmish application:
It is also possible to create your own commands.
For example, Elmish.Browser is offering you several
Refine your user experience
You can combine all the techniques I spoke about here to improve the user exeperience of Elmish.
For example, Thoth.Elmish.Toast is a library adding global notification support for any component in an Elmish application. It uses:
- Program composition to register the library logic and views
- Custom commands in order to send a notification to the program directly
- F# feature to provide a
Toastbuilder interface using pipes
Elmish is a really powerful library and makes good use of the standard F# features like function composition, partial application, guides you to write safe code (handling success and error case).
It also offers you powerful customization if the standard features are not enough.
I hope you found this article useful and that it will help you. If you want another article to describe a specific subject please comments below 😊.