DEV Community

Jason
Jason

Posted on • Updated on • Originally published at rametta.org

Elm's Remote Data Type in Javascript

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)
Enter fullscreen mode Exit fullscreen mode

and showing it on screen:

// React

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

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
}
Enter fullscreen mode Exit fullscreen mode

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>
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 ]
Enter fullscreen mode Exit fullscreen mode

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']
})
Enter fullscreen mode Exit fullscreen mode

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))
Enter fullscreen mode Exit fullscreen mode

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>
})
Enter fullscreen mode Exit fullscreen mode

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!

Top comments (5)

Collapse
 
_andys8 profile image
Andy

Nice :)

In TypeScript: github.com/devex-web-frontend/remo...

Collapse
 
valeriashpiner profile image
Valeria

Thank you for the article 💛
Very clear! I like it :)

Collapse
 
josephthecoder profile image
Joseph Stevens

This is the article that got me into Elm. Thank you for sharing, this is next level. Cardinality is a brilliant way to articulate this approach!

Collapse
 
sdyalor profile image
sdyalor

Nice work ;3

Collapse
 
sushruth profile image
Sushruth Shastry

Great stuff. Also, the same could be achieved with a discriminated union type, for those working with typescript.