Storing and Restoring the State in elm@0.17

Danny Arnold
Wundernerds Blog
Published in
3 min readMay 31, 2016

If you’re just interested in the code: https://github.com/despairblue/counter-local-storage

It’s quite common for elm applications to save the application’s state in local storage and reinitialize themselfs with that state on reload. dreamwriter (still using elm@0.15) is doing it, the elm-todomvc example does it.

Ports

Ports are used to achieve this. Ports are a way for elm programs to communicate with JavaScript in a coordinated and safe fashion.

With the release of elm 0.17 the way ports work changed.

Restoring the Model

The JavaScript-side would look like this:

// elm@0.16
const model = localStorage.getItem('model')
const startingModel = model ? JSON.parse(model) : null
const elmApp = elm.fullscreen(elm.Main, { getStorage: startingModel })

Usually one would have declared an incoming port like this:

-- elm@0.16
port getStorage : Maybe Model

That port could contain our stored model. After pattern-matching it out this can be passed into a Signal.foldp as an initial value, but enough about elm 0.16. In elm 0.17 that changed and — looking at my old code and trying to understand it just enough to write this — for the better.

In JavaScript the way an application is initialized changed a bit, though that has nothing to do with ports:

// elm@0.17
// this stayed the same
const model = localStorage.getItem('model')
const startingModel = model ? JSON.parse(model) : null
// this changed, `fullscreen` is defined on the module itself
const elmApp = elm.Main.fullscreen(startingState)

The way the port is declared in elm also changed.

It’s gone. There is no incoming port for things passed in when creating an application.

Instead you have to use a special type of Program, Html.App.programWithFlags:

-- elm@0.17
main : Program (Maybe Model)
main =
Html.programWithFlags
{ init = init
, update = update
, subscriptions = subscriptions
, view = view
}

As can be seen in the type annotation it takes a Maybe Model as an argument.

The Model gets passed to init. So init becomes a function:

-- elm@0.17
init : Maybe Model -> ( Model, Cmd msg )
init model =
case model of
Just model ->
( model, Cmd.none )
Nothing ->
( defaultModel, Cmd.none )

So that’s the easy part. Now to the other easy part. The one that makes it worthwhile: saving the model on every update.

Storing the Model

The JavaScript interface stayed the same:

// elm@0.16 and elm@0.17
elmApp.ports.setStorage.subscribe(function (state) {
localStorage.setItem('model', JSON.stringify(state))
})

In elm 0.16 you would declare an outgoing port like this:

-- elm@0.16
port setStorage : Signal Model
port setStorage =
model

In elm 0.17 a couple of things are a bit different. The most noticeable difference on the elm-side being that a module that uses ports must declare this at the module declaration by using the port keyword:

-- elm@0.17
port module Main exposing (..)

Also the ports signature change due to the transition from Signals to Subscriptions:

-- elm@0.17
port setStorage : Model -> Cmd msg

setStorage takes a Model and returns a Cmd that can be returned from update. That’s basically it, there is just some wiring to do:

-- elm@0.17
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
let
newModel =
case msg of
Increment ->
model + 1
Decrement ->
model — 1
in
( newModel, setStorage newModel )

setStorage model returns a Cmd and thus replaces the Cmd.none part. If you want to return multiple Cmd`s have a look at Cmd.batch and the withSetStorage helper function in elm-todomvc

--

--