In the previous section, we created an app whose view looked like this before clicking the Get data from server button:
It’s not considered a good UI practice to show the Posts heading and table headers when we haven’t even fetched any data yet. We need to hide those until the data is retrieved. Before we do that though let’s look at different states an HTTP request tends to be in.
Before the Get data from server button is clicked the request for fetching posts is in the Not Asked
state because we haven’t asked for that data yet. When the button is clicked the request transitions to the Loading
state. If the request is successful, it ends up in the Success
state. If not, it moves to the Failure
state.
So far we have only dealt with the Success
and Failure
states. Once we handle the remaining two, our UI will be in a much better shape. We will be using a third-party package called krisajenkins/remotedata
to handle the remaining states. Go ahead and install it by running the following command from the beginning-elm
directory in terminal.
Answer y
when asked to update elm.json
. After that, import the RemoteData
module in DecodingJson.elm
.
Handling Not Asked State
The RemoteData
module provides a type by the same name.
Note: It’s perfectly fine to use the same name for a module and a type. In fact you will see this pattern over and over again with many built-in modules such as Array, Html, and Task.
Aha! The four data constructors look identical to different states we saw earlier. We need to modify several parts of our app to be able to take advantage of RemoteData
. What follows is a step-by-step guide for making those changes.
Step 1: Modify the Model
The first thing we need to do is change our model. Currently, we are directly assigning a list to the posts
field.
We need to wrap that list with RemoteData
. Go ahead and change the Model
type in DecodingJson.elm
to this:
We removed the errorMessage
field because any potential error now resides in the posts
field itself. We can simplify the type of posts
by using WebData
instead.
WebData
is defined in the RemoteData
module as a type alias.
WebData
represents data fetched from an HTTP (also known as Web) server like ours. That’s why the error type is hard-coded to Http.Error
. If we were retrieving data from a non-HTTP server such as FTP, we would have to use the RemoteData
type instead of WebData
. All non-HTTP requests also go through the same four states we covered earlier.
Step 2: Modify init
Currently, we initialize the posts
field to an empty list.
It doesn’t make sense to assign an empty list to posts
from the get go. What if the server responds with an empty list indicating there are genuinely no posts in the database? How do we differentiate between that scenario and not having requested data in the first place? The answer is to use NotAsked
instead of an empty list. Let’s replace []
with RemoteData.NotAsked
and remove errorMessage
from init
.
Step 3: Modify DataReceived’s Payload Type
Next we need to replace the Result
type in DataReceived
message’s payload with WebData
.
Step 4: Convert Result to RemoteData Value
Here is how we’re creating a command for fetching posts right now:
The type of the expect
field in Http.get
depends on the type of our DataReceived
message.
Before we introduced RemoteData
, DataReceived
had the following type.
Now DataReceived
’s type has changed to this:
Since WebData
is just a type alias for RemoteData
, the above type signature is equivalent to this:
This means the Result
type must be converted to RemoteData
. The RemoteData.fromResult
function is just what we need. Here is how its type signature looks:
Update httpCommand
in DecodingJson.elm
to this:
As mentioned in the Using » Operator section in chapter 5, >>
is a built-in operator for composing multiple functions. Here is an example:
So conceptually we can think of RemoteData.fromResult >> DataReceived
as:
Remember, all messages that contain a payload are essentially functions behind the scenes. That’s why we were able to use >>
with DataReceived
.
Step 5: Modify update
DataReceived
’s payload is now of type WebData
instead of Result
. That means we can replace the DataReceived (Ok posts) ->
and DataReceived (Err httpError) ->
branches in update
with something much simpler.
All we’re doing in the DataReceived response ->
branch is assign whatever response
we get to the posts
field in our model.
Step 6: Modify View Code
The only remaining change is to handle different states in our view code. Modify the viewPostsOrError
function in DecodingJson.elm
to this:
Previously, we were checking for the presence of an error message to determine whether to display posts or an error view.
Now we’re determining which view to display based on different states defined in the RemoteData
type. We’re ready to test our app. Run elm reactor
from the beginning-elm
directory in terminal if it’s not running already and go to this URL in your browser: http://localhost:8000/src/DecodingJson.elm
. The only element you should see is a button.
Transitioning to the Loading State
Run json-server
from the beginning-elm
directory in terminal using the following command if it’s not running already.
Click the Get data from server button and you should immediately see a table containing posts. Although the HTTP request transitions to Loading state after the button is clicked, we don’t see Loading...
on the page. For that text to appear, we need to make our server wait a couple of seconds before returning a response. Stop json-server
by pressing Ctrl + c
and restart it with the --delay
option.
--delay
takes the number of milliseconds as an argument. json-server
will now wait for a second before responding to all requests. Refresh the page at http://localhost:8000/src/DecodingJson.elm
and click the Get data from server button.
Hmm… We’re still not seeing Loading...
. We forgot to change the state from NotAsked
to Loading
in update
before firing off the HTTP command. RemoteData
doesn’t transition to Loading automatically. We need to do that manually inside the SendHttpRequest ->
branch in update
.
Refresh the page at http://localhost:8000/src/DecodingJson.elm
one more time and click the Get data from server button. You should now see Loading...
while the posts are being fetched.
Transitioning to the Failure State
Change the URL inside httpCommand
in DecodingJson.elm
to something invalid.
Since we’re requesting a non-existent resource, the HTTP request will eventually transition to the Failure
state. Refresh the page at http://localhost:8000/src/DecodingJson.elm
and click the Get data from server button. You should see the following error message.
Don’t forget to change the URL back to http://localhost:5019/posts
in httpCommand
.
Summary
In this section, we used a third-party package called krisajenkins/remotedata
to improve our UI by properly handling all four states an HTTP request can be in at any given time. Those four states are: NotAsked
, Loading
, Success
, and Failure
. Kris Jenkins — the author of that package — has written a wonderful blog post explaining the rationale behind creating RemoteData
. I highly recommend you read it.
In the next section, we will learn how to send an HTTP command when the app is being initialized. Here is the entire code from DecodingJson.elm
thus far: