Slaying a UI Antipattern in Fantasyland

This write-up is based on Kris Jenkins excellent post about how Elm slays a UI antipattern.

The problem he presents is a very common one. You are loading a list of things but instead of showing a loading indicator you just see zero items. In JavaScript your data model may look like this:

{ loading: true, items: [] }

But of course it’s easy to forget to check the loading flag or maybe you just can’t be bothered right now because of time constraints and “will do it later”. It also just makes for awkward code everywhere. Setting the items to null instead of an empty array creates even more problems so that won’t help either.

Kris Jenkins presents a very elegant solution by using Elm’s algebraic type system. While we don’t have anything like this in JavaScript we can still do much better by using functional Fantasyland libraries like folktale and daggy.

Maybe use Maybe?

The first solution uses a simple Maybe. A Maybe is just a container for a value that may not be present. It has two cases. The Nothing case where we don’t have any value and the Just case where we do have a value.

Folktale already provides this type with data.maybe.

import { Just, Nothing } from 'folktale/data/maybe'
// in our React component constructor
this.state = { items: Nothing() }

With this container type our model will be an instance of a Maybe that, if it has a value, contains our list of items. Initially it will be Nothing and once we have a result we can assign it to items wrapped in a Just.

this.setState({ items: Just(['foo', 'bar', 'baz']) })

In our view code we can then use the cata method of the Maybe instance to handle the two cases accordingly:

// in our render function
return this.state.items.cata({
Just: ({ value }) => this.renderItems(value),
Nothing: () => this.renderLoading()
})

This is much better then the separate loading flag and while we don’t have a type system to yell at us if we miss a case at least it will be caught at runtime. It also makes for a much clearer data model and more readable code.

Level-Up

The Maybe solution is good already but we can do even better. Kris Jenkins identifies four states that this data model can be in which are the initial state called NotAsked, a Loading state, a Failure state with some error e and a Success state with some value a.

The type definition for this type called RemoteData in his post is

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

where e and a are type parameters so it can be used with any result type a and any error type e.

We can emulate this with daggy or folktale’s core.adt. We’ll use daggy for this example just because.

const RemoteData = daggy.taggedSum({
NotAsked: [],
Loading: [],
Failure: ['error'],
Success: ['items'],
})
// in our constructor
this.state = { items: RemoteData.NotAsked }

In our view code we can then handle all the cases accordingly:

// in our render function for example
return this.state.items.cata({
NotAsked: () => this.renderInitial(),
Loading: () => this.renderLoading(),
Failure: (error) => this.renderError(error),
Success: (items) => this.renderItems(items),
})

Links

http://blog.jenkster.com/2016/06/how-elm-slays-a-ui-antipattern.html

http://blog.jenkster.com/2016/06/functional-mumbo-jumbo-adts.html

http://folktalejs.org/

https://github.com/puffnfresh/daggy

Classroom Coding with Prof. Frisby