Elm Tricks from Production–Declarative, Bug-Free User Interfaces with Custom Types

riccardoodone

Riccardo Odone

Posted on July 20, 2020

Elm Tricks from Production–Declarative, Bug-Free User Interfaces with Custom Types

You can keep reading here or jump to my blog to get the full experience, including the wonderful pink, blue and white palette.


There are 4 possible statuses when it comes to fetching data in a frontend application:

  • before fetching
  • while fetching
  • successful fetch
  • failed fetch

In JavaScript it's easy to either forget handling some statuses or to declare cleanly what to render on screen for each status. This opens the gates to a lot of bugs. Ever been on a page with a spinner that never disappears?

Elm solves both the problems defined above in perfect functional-programming style, adding a type. In particular, let's say we want to create a new cool webapp. It will show a random number every time the page is refreshed, cool right?! We can use a public API to fetch the random number. Also, we need to choose a type, let’s go with Int. Unfortunately, the random number API is really slow so we need to show a message while fetching. We could use 0 to mark the fact that we still don't have the random number:

case number of
  0 -> Html.text "Loading..."
  i -> Html.text ("The random number is: " ++ String.fromInt i)
Enter fullscreen mode Exit fullscreen mode

But this is not a good solution: if the API returns 0 as a random number we will be showing the loading message forever! Let's try with Maybe Int:

case maybeNumber of
  Nothing -> Html.text "Loading..."
  Just i  -> Html.text ("The random number is: " ++ String.fromInt i)
Enter fullscreen mode Exit fullscreen mode

Cool, now we can distinguish between not having the number or having it. What if we got an error though? Let's add a flag for that:

case (maybeNumber, hasFailed) of
  (Nothing, False) -> Html.text "Loading..."
  (Just i, False)  -> Html.text ("The random number is: " ++ String.fromInt i) 
  (Nothing, True)  -> Html.text "Could not fetch random number" 
  (Just i, True)   -> WTH??
Enter fullscreen mode Exit fullscreen mode

Problem is there's a combination that does not make sense at all (i.e. (Just i, True)): we fetched a number but there's an error. Custom type to the rescue:

type RemoteData
  = Loading
  | Success Int
  | Failure

case remoteDataNumber of
  Loading   -> Html.text "Loading..."
  Success i -> Html.text ("The random number is: " ++ String.fromInt i) 
  Failure   -> Html.text "Could not fetch random number"
Enter fullscreen mode Exit fullscreen mode

Now, let's say that instead of loading the random number every time the page is refreshed, we want to start fetching as soon as a button is clicked:

type RemoteData
  = NotAsked
  | Loading
  | Success Int
  | Failure

case remoteDataNumber of
  NotAsked  -> Html.text "Click the button!"
  Loading   -> Html.text "Loading..."
  Success i -> Html.text ("The random number is: " ++ String.fromInt i) 
  Failure -> Html.text "Could not fetch random number"
Enter fullscreen mode Exit fullscreen mode

The beauty of this solution is that, as soon as we declare our random number to be of RemoteData type, the Elm compiler will enforce checking all the branches. Also, it's really easy to see what the app is doing depending on the status of the request.

AirCasting uses the pattern shown above extensively. In particular, to avoid reinventing the wheel we used RemoteData. One example is the function that takes care of rendering either the sessions list or the selected session at the bottom of the map page:

viewSessionsOrSelectedSession selectedSession =
        div []
            [ case selectedSession of
                NotAsked ->
                    viewSessions

                Success session ->
                    viewSelectedSession (Just session)

                Loading ->
                    viewSelectedSession Nothing

                Failure _ ->
                    div [] [ text "error!" ]
            ]
Enter fullscreen mode Exit fullscreen mode

In other words, if the request for the selected session is not ongoing, show all the sessions. If the selected session is loading show its view while waiting for the data to come. If the request was successful show the selected session view for it. If the request failed show the error message.

If you are not using Elm you can still apply the pattern successfully. Unfortunately, there won’t be a nice compiler helping guaranteeing an error free application 😉.


Get the latest content via email from me personally. Reply with your thoughts. Let's learn from each other. Subscribe to my PinkLetter!

💖 💪 🙅 🚩
riccardoodone
Riccardo Odone

Posted on July 20, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related