In this section, we’ll retrieve the app state saved in browser’s local storage and initialize our Elm app with it.
Retrieving App State
Let’s start with JavaScript this time. Add the code for retrieving posts right above the line that creates the app
variable in beginning-elm/index.html
.
The localStorage.getItem
function retrieves our app’s state which is stored in raw JSON string format. post-app-save
is the same key we used for saving the state.
After retrieving the sate, we want to print it in the browser console so that we can see what it looks like. If the state does exist, the raw JSON string is parsed into JavaScript values using the JSON.parse
function.
Sending App State to Elm via Flags
Next we need to pass startingState
to our Elm app during initialization. How do we do that? By using flags. Update the line that initializes our Elm app in beginning-elm/index.html
like this:
Flags are different from an incoming port which creates a subscription. Flags on the other hand are delivered directly to the initialization function assigned to the init
field in main
.
Just like an incoming port, the JavaScript values passed as flags are automatically decoded by the runtime into corresponding Elm values. Let’s change the type of flags
in post-app/Main.elm
from ()
to Maybe String
.
Flags are always passed as the first argument to the init
function. Their type varies between applications. In our case, it’s Maybe String
. It is possible that the app state might not have been saved in local storage. In that case, JavaScript will send null
. To accommodate that, we need to use the Maybe
type. Remember, null
in JavaScript gets translated to Nothing
in Elm.
We also need to replace ()
with Maybe String
in the main
function’s type annotation.
As discussed in the main Function’s Type Annotation section from chapter 5, by using ()
— unit type — we were letting the compiler know that no values were passed to our app during initialization. But since we are actually sending flags
from JavaScript now we need to reflect that in main
’s type annotation.
Decoding Stored Posts JSON
Now we need to decode the JSON string into a list of Post
. Let’s add a function called decodeStoredPosts
below init
in Main.elm
.
We’re using postsDecoder
from Post.elm
to decode JSON. decodeString
produces a Result
. If the process of decoding is successful we need to wrap posts
in WebData
type by calling the RemoteData.succeed
function. Otherwise, we simply return the loading status. Later in ListPosts.elm
, we’ll be fetching new posts right away if stored posts aren’t available and we need to display the Loading...
text while the request is in flight.
Note: If you don’t remember how decodeString
works, you may want to review the Decoding JSON section from chapter 6.
We need to import the Json.Decode
, Post
, and RemoteData
modules in Main.elm
.
Now let’s use decodeStoredPosts
in Main.init
.
Passing Stored Posts to ListPosts Page
Next we need to pass the stored posts
to ListPosts.init
inside initCurrentPage
in Main.elm
.
Currently ListPosts.init
doesn’t take posts
as an input. Let’s modify it in ListPosts.elm
.
Next we need to pass RemoteData.Loading
as stored posts to initCurrentPage
inside the ( UrlChanged url, _ ) ->
branch in Main.update
Testing
We are now ready to test. Run the following command from the beginning-elm
directory in terminal to compile the app.
You shouldn’t see any errors. Also start json-server
by running the following command from the beginning-elm
directory in a separate terminal window.
Now go to http://localhost:8000/
in a browser and open the browser console. You should see the retrieved state printed as JSON in the console.
How do we verify that our app did get initialized with the stored state? Let’s stop json-server
by pressing Ctrl + c
and see how the app behaves when the server is unreachable. Refresh the page at http://localhost:8000/
.
Unfortunately, we see an error instead of the posts retrieved from local storage. How about we don’t fetch the posts from a server when the app is initialized? That way if we see a list of posts when the app is launched and the server is not running, we’ll definitely know that those posts are from local storage. Update the init
function in ListPosts.elm
like so:
Now if you refresh the page at http://localhost:8000/
, you should see the posts. Those are definitely loaded from local storage.
Bring json-sever
back up by running the following command from the beginning-elm
directory in terminal.
Click the Refresh posts button from the home page. If you open the browser console, you should see both the saved and retrieved states. Whenever we retrieve posts from the server, they immediately get saved in local storage.
Verifying Flags
Let’s see what happens if we try to pass a non-string value to Elm using flags. Inside beginner-elm/index.html
, replace startingState
with 10
in line that initializes our Elm app.
Now refresh the page at http://localhost:8000/
. You should see the following error in browser console.
Our Main.init
function is expecting flags to be of type Maybe String
, but we sent a number instead. Flags must be exactly what the init
function expects, otherwise Elm throws an error on JavaScript side. Without this check, we could pass anything leading to runtime errors in Elm.
It’d be better to show a user friendly error message instead of crashing our app like this. To do that, we need to convert the JavaScript values to Elm values ourselves by changing the flags’ type from Maybe String
to Value
in Main.init
.
We also need to change Maybe String
to Value
in the main
function’s type annotation.
As mentioned in Protecting Boundaries between Elm and JavaScript, the Value
type represents a JSON value. To decode a JSON value ourselves, we need to use the decodeValue
function. Replace the logic for computing posts
in let
area of Main.init
with the following.
If a flag of invalid type is sent from JavaScript, we need to create an error. The WebData
type’s definition indicates that RemoteData.Failure
requires the error to be of type Http.Error
. That’s why we’re using the Http.BadBody
data constructor to build an error.
Let’s import the Http
module and expose Value
and decodeValue
from Json.Decode
in Main.elm
.
Stop json-server
and refresh the page at http://localhost:8000/
and you shouldn’t see the decoding error in console anymore. Unfortunately, the error we see on the page says Unable to reach server
instead of Flags must be either string or null
. That’s because in ListPosts.init
we’re firing the command for fetching posts unless the posts
parameter is RemoteData.Success
.
Let’s also not fire the fetchPosts
command when the posts
parameter is RemoteData.Failure
by updating the if
expression to the following.
Refresh the page at http://localhost:8000/
one more time and you should see the Flags must be either string or null
error.
Summary
In this section, we learned how to retrieve the state of our app stored in browser’s local storage. We used the JavaScript function localStorage.getItem()
to retrieve the state and sent it to our Elm app via flags. Elm runtime automatically decodes JavaScript values inside flags into corresponding Elm values before passing them to the init
function. A more robust approach is to receive flags as Value
and decode it ourselves.