Jason Rametta Blog

Elm's Remote Data Type in Javascript

July 26, 2019

Often in web development there is this recurring pattern of having to fetch some data from some server through a rest api, and then show it someway in the UI.

This often includes storing this data somewhere on the client side, either in a store or just a variable you can reference, and this is where the Remote Data type can help.

Usually saving the data would look something like this in JS:

// Javascript

const state = {
  data: null,
  error: null
}

fetch('/api/data')
  .then(res => res.json())
  .then(data => state.data = data)
  .catch(err => state.error = err)

and showing it on screen:

// React

const MyComp = ({ error, data }) => {
  if (error) {
    return <div className="error">{error}</div>
  } else {
    return <div>{data}</div>
  }
}

But there are a few problems with this approach:

  1. What do we show on screen when the data is loading from the server?
  2. What do we show on screen before we even request the data from the server?
  3. Do we properly clear the previous error if the data is a success?
  4. Do we properly clear the previous data if we get an error?

So how can we solve this?

Some might recommend we can add more fields to our state to help represent all the cases, like this:

// Javascript

const state = {
  data: null,
  error: null,
  loading: false,
  isAsked: false
}

and in the UI would be similar to this:

// React

const MyComp = ({ error, data, loading, isAsked }) => {
  if (!isAsked) {
    return <div>Nothing asked yet</div>
  }

  if (loading) {
    return <div>Loading...</div>
  }

  if (error) {
    return <div className="error">{error}</div>
  }
  
  if (data) {
    return <div>{data}</div>
  }

  return <div>Some default fallback to be safe</div>
}

But the problem with this is that it becomes too easy for our UI to accidently show the wrong case by forgetting to set loading to false after the data comes back as an error, or forgetting to clear the error if a retry returned a success.

Infact, optimistically the structure above can have a cardinality of 2 x 2 x 2 x 2 which is 16 possible different combinations of states we can be in at any given time. That is a lot of cases to represent.

Let’s look at how Elm simplifies this process with needing only 4 cases.

Remote Data in Elm

The Remote Data type can be created manually by writing a custom type like below, or by using a pre-made version from a library like krisajenkins/remotedata:

-- Elm

type RemoteData e a
  = NotAsked
  | Loading
  | Error e
  | Success a

and then using this type as your model:

-- Elm

type alias Model = RemoteData String MyData

-- and set the initial model value as NotAsked

init = NotAsked

What we need to understand is that our state can only be one of these 4 types at any time. We can not both be in a Loading state and in a Success state at the same time, or else our UI will be showing 2 different things. This is why our cardinality is only 4 now instead of 16, because there is no way to represent our state more than that.

Using this model we can now create a UI for each case like this:

-- Elm

view model =
  case model of
    NotAsked -> div [] [ text "Not asked yet" ]

    Loading -> div [] [ text "Loading..." ]

    Error err -> div [] [ text err ]

    Success data -> div [] [ text <| "Here is my data: " ++ data ]

And now whenever we update our state, we never need to worry about clearing previous state or forgetting to flip one field to null - because there is only one field in our model.

This is a great pattern for handling remote data fetching in Elm, but how do we take advantage of this pattern in Javascript? Daggy.

Daggy

There are a few different libraries in Javascript that can help model your application with Algebraic Data Types like we have in Elm with the type keyword. One popular library is Daggy.

To represent remote data in Daggy, we would create a type like this:

// Javascript

import { taggedSum } from 'daggy'

const RemoteData = taggedSum('RemoteData', {
  NotAsked: [],
  Loading: [],
  Error: ['e'],
  Success: ['a']
})

Then once we have our type, the implementation is almost identical to how we would work in Elm.

Our state would only have one field instead of 4, and a cardinality of 4 instead of 16.

// Javascript

const state = {
  data: RemoteData.NotAsked
}

// Fetch some data

state.data = RemoteData.Loading

fetch('/api/data')
  .then(res => res.json())
  .then(data => state.data = RemoteData.Success(data))
  .catch(err => state.data = RemoteData.Error(err))

And in our UI, like React, we would have:

// React

const MyComp = ({ data}) => data.cata({
  NotAsked: () => <div>Not asked yet</div>,
  Loading: () => <div>Loading...</div>,
  Error: err => <div>{err}</div>,
  Success: d => <div>Here is my data: {d}</div>
})

Using this pattern actually helps move a lot of render logic out of your components in React, like checking fields with if (field) {...} and instead moves that responsibility to something like a reducer, which will make it a lot easier to run unit tests on.

If you would like to learn more about Algebraic Data Types in Javascript, check out these links:

If you like this article, be sure to follow me!


Jason Rametta

Written by Jason Rametta who lives and works in Montreal building useful things. You should follow him on Twitter