Communicating between MVU states

Learn how to use the Intent pattern to communicate between independent MVU states

Like we saw in the previous sections, most MVU apps adopt the following signatures to model how their state changes and the side effects to be executed:

val init : unit -> Model * Cmd<Msg>
val update : Msg -> Model -> Model * Cmd<Msg>

MVU programs are meant to be well isolated from the outside world to be reliable and predictable, producing only pure init and update functions. But how does a program communicate with other programs when something goes out of its scope such as navigating to another page?

Communicating from child to parent

While technically the parent could peek into the child's Model to eventually react to a specific state and do additional work (see Splitting into independent MVU states). This approach is pretty bad because it depends greatly on the internal implementation of the child which can change at any time.

Since we know the parent will always call the child's init and update functions, we can take advantage of this by returning a new value specifically for the parent.

Comes in the Intent pattern:

val init: unit -> Model * Cmd<Msg> * Intent option
val update: Msg -> Model -> Model * Cmd<Msg> * Intent option

As you can see, we are returning one more value after executing init and update: Intent option.

The Intent is a discriminated union created by you just like Msg. Its goal is to notify the caller of extra intention that needs to be taken care of outside the child.

Let's take the example of an app going through several pages of forms that the user needs to fill in.

The App module will take care of the whole workflow between the pages, but each individual page has its own MVU state. By returning an intent, the page can notify the app to do cross-page actions.

Form1.fs
type Msg =
    | TextChanged of string
    | Complete

type Intent =
    | SaveDraft of string
    | GoToNextStep
    
let init () =
    { ... }, Cmd.none, None
    
let update msg model =
    match msg with
    | TextChanged newValue -> { model with Text = newValue }, Cmd.none, Some (SaveDraft draft)
    | Complete -> model, Cmd.none, Some GoToNextStep

Communicating between siblings

Just like parent-child communication, communicating between siblings involves using an intent. Except the intent this time tells the parent to forward a message to the child's sibling.

To learn more about the Intent pattern, please read The Elmish Book - The Intent Pattern.

Last updated