Storing and Restoring the State in elm@0.17
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