
A better way to represent loading state in ReactJS/Redux apps - NikolayN
http://nikolay.rocks/2017-06-18-better-redux-loading
======
jamesisaac
> Option 1: Use a string or null/false data [...] If you're using a type
> checker like #flow, it will yell at you and for a good reason: data type
> changes are hard to reason about and very error prone in practice.

This isn't true. Flow is great at enforcing that all potential types/values
are handled in an if statement. If the following type was used, the code
missing the failure case would have been picked up by Flow:

    
    
        type Pics = 'fetching' | 'failed' | Array<Pic>
    

> Option 2: use an object with two props [...] someone accidentally changes
> the status without changing the data. Or the other way around. This will
> result in nasty and hard to debug issues where the state and data don't
> match visually.

Again, this can be avoided by using the correct union in Flow, which will pick
up right away if the two properties have become inconsistent:

    
    
        type PicsState =
          | { status: 'fetching', list: null }
          | { status: 'failed', list: null }
          | { status: 'fetched', list: Array<Pic> }

~~~
michaelmartin
Hadn't seen the union approach in #2 before, I like that a lot. The 2 named
properties are intuitive to other developers reading it, but now we also get
compile-time safety that the combinations they use are valid. Thanks!

For anyone else that this was new to, Flow has documented these as "disjoint
unions": [https://flow.org/en/docs/types/unions/#toc-disjoint-
unions](https://flow.org/en/docs/types/unions/#toc-disjoint-unions)

------
pg_bot
I would propose a fourth option, use two reducers to decouple loading of data
with actual data. Both reducers listen to the same events and update
independently. Your data ends up looking like { picturesLoading: false/true,
pictures: [] }, which everyone can understand. Your reducers look like:

    
    
        const picsLoadingReducer = (state = false, action) => {
          switch (action.type) {
            case PICS_REQUEST: return true;
            case PICS_FAILURE: return false;
            case PICS_SUCCESS: return false;
            default: return state;
          }
        };
    
        const picsReducer = (state = [], action) => {
          switch (action.type) {
            case PICS_FAILURE: return action.payload;
            case PICS_SUCCESS: return action.payload;
            default: return state;
          }
        };
    

IMO this is a bit more simple since we don't mix two concepts (am I loading
data, what is the data to be rendered).

~~~
ncphillips
This is effectively the same solution as the authors second option.

~~~
pg_bot
Yep! However the reason why I've written this was not to demonstrate my own
creativity it was more to show that the concerns that the author had with
option 2 disappear when you approach the problem from a different perspective.
Your codebase doesn't become more complex as you add more actions since the
loading reducer doesn't need to change. You are also less likely to run into
bugs since a boolean is a better primitive for the behavior he is describing.

------
mpodlasin
This is actually pretty bad. In redux dev tools you will not see if data is
being fetched or failure occured. Also, if you serialize and deserialize state
object - say save it in local storage - empty array will be neither fetching
nor failure one and strange bugs will occur.

~~~
andreareina
I'm only passingly familiar with redux (read the tutorial + parts of the docs
a few times, never actually used it), where is the problem? As I understand it
the status of the request is encoded in the store's state. The
(de)serialization can be handled as part of the fetch:

    
    
        const fetchPics = () => dispatch => {
          dispatch({ type: PICS_REQUEST });
          const cached = deserialize(localStorage.get('pics'));
          if (cached && cached.length > 0) return dispatch({ type: PICS_SUCCESS, ...});
    
          return api.get('/pics')
            ...
        }

~~~
alangpierce
The problem is if you try to serialize and deserialize the entire state tree
itself, including the loading/failed state. LOADING_STATE, FAILURE_STATE, and
an empty array all convert to the same JSON, so if you take your entire state
tree, save to JSON somewhere, then load it back, you won't get what you want.

The ability to take your whole application state, save it somewhere (e.g.
localstorage), then load it back later is one of the cool things enabled by
redux, and it also makes it easier to implement things like time-travel
debugging.

~~~
andreareina
I can see that. Thanks!

------
wereHamster
Use ADTs, described on a high level with something like this:

    
    
        data Pics
            = PicsLoading
            | PicsFailed
            | PicsLoaded (Array Pic)
    

Now you only need to figure out how you want to map it into assembler (eh,
JavaScript). But that's largely a mechanical task. Like for example a compiler
does.

Use a object with a tag field (like advocated by redux and well-supported in
flow/typescript), or an array where the first element is a numeric tag and the
rest payload (that's roughly the bytecode that a Haskell compiler would
produce), or, god forbid, classes with instanceof checks or abstract class
methods (hello Java).

------
maaaats
We've extracted something like this logic into a higher order function that we
use to generate reducers++.

Fetching data with Redux is kinda broken, though. If a dispatch is done in
componentWillMount, a synchronous update like setting a loading flag is not
visible in the first render, so most spinners have subtle bugs. Kinda crazy
that this common use case is not supported.

~~~
jamesisaac
Why use componentWillMount and not componentDidMount? From the docs:

> componentWillMount() is invoked immediately before mounting occurs. It is
> called before render(), therefore setting state synchronously in this method
> will not trigger a re-rendering. Avoid introducing any side-effects or
> subscriptions in this method.

> componentDidMount() is invoked immediately after a component is mounted.
> Initialization that requires DOM nodes should go here. If you need to load
> data from a remote endpoint, this is a good place to instantiate the network
> request. Setting state in this method will trigger a re-rendering.

~~~
maaaats
My point isn't so much loading data from a remote endpoint, but setting state.
As the doc you quote says: componentWillMount should not trigger rerendering,
and should have the newest state in the render function.

But when you use Redux this isn't true! The first render will see the old
state of the store even if you did changes to it right befofe. And then you
immediately get a new rerender with the correct state. This cannot be fixed
with how react-redux is currently implemented, and leads to subtle bugs,
especially when unmounting and remounting stuff that tries to clear some
state.

Edit: more details [https://github.com/reactjs/react-
redux/issues/210](https://github.com/reactjs/react-redux/issues/210)

~~~
jamesisaac
As Dan said when closing that issue, this seems to me how I would expect
rendering in React to work... it's async by nature.

If you need the component's state/props to be a certain way for the very first
render, use `this.state = {...}` in the contructor, or wait until the Redux
store is in the correct shape before mounting the component. (I'm guessing
this is what you've done in your HOC, so this sounds like the right solution
to me).

Calls like `dispatch` and `setState` shouldn't be expected to reflect in a
component's rendering synchronously. And as far as I know, as of Fiber,
componentWillMount is even going to become a method that could be called
multiple times before the component actually mounts... its usage is pretty
widely discouraged for most cases:
[https://github.com/facebook/react/issues/7671](https://github.com/facebook/react/issues/7671)

------
yiransheng
With algebraic data type like in Haskell, the state could be typed like this:

    
    
        data State a = 
          Loading
          | Failed
          | Loaded [a]
    

Compared to a representation like `{ loading:true, error: false, pics:[] }`,
the above model has the benefit of "making illegal state not representable".

One library in js I like to use to model this type of tagged union / ADT
values is single-key[1]. Each variant in a tagged union type is represented by
an plain object with just one key, for example:

    
    
        const state1 = { Loading: null };
        const state2 = { Failed : <reason> };
        const state3 = { Loaded : [...pics] };
    
    

The library offers pattern matching syntax on top of this data representation:

    
    
        // render
        const reactElement = match(state, {
          Loading : () => <Loading />,
          Failed  : (reason) => <Error {...reason}/>,
          Loaded  : (pics) => <PicsList pics={pics} />
        });
    
        // reducer
        const reducer = (state, action) => match(state, {
            Loading : () => {
              switch (action.type) {
                case PICS_FAILURE:
                  return { Failure: action.payload };
                case PICS_SUCCESS:
                  return { Loaded : action.payload };
                default:
                  return state
              }
            },
            Failed : () => {
              switch (action.type) {
                case PICS_REQUEST:
                  return { Loading: true };
                default:
                  return state;
              }
            },
            Loaded : () => {
              switch (action.type) {
                case PICS_REQUEST:
                  return { Loading: true };
                default:
                  return state;
              }
            }
          });
    
        

[1] [https://www.npmjs.com/package/single-
key](https://www.npmjs.com/package/single-key)

------
kuon
This is exactly the kind of issue that go away with elm.

[http://blog.jenkster.com/2016/06/how-elm-slays-a-ui-
antipatt...](http://blog.jenkster.com/2016/06/how-elm-slays-a-ui-
antipattern.html)

~~~
errnesto
I would say the nice thing here is the type system.

I always use `null` to differentiate between an empty list or string and
nothing.

I like when I have a type system that ensures I check for null etc. But I only
work on very small projects so this works well in js for me as well.

------
machiaweliczny
I think that this issue is nicely solved by [https://github.com/arunoda/react-
komposer](https://github.com/arunoda/react-komposer)

------
yeasayer
Relying on JS illogical behaviour:

    
    
      [] === [] // false
    

It's more confusing and error prone than other options mentioned in the
article.

~~~
inimino
I understand it's popular to heap abuse on any and all JS features, but please
don't jump on that bandwagon.

There's nothing whatsoever illogical about two mutable objects that are not
identical being reported as unequal, and it's hard to think of any language
that has mutable values at all where there isn't an equivalent feature.

------
barrystaes
These solutions only have one status for the entire list, not per item.

Its common to only get a partial list (e.g. a search, or paginated) or
read/update just one item. I expected this "better way" to accomodate for
that. The oldest "flux" examples already did.

------
mrredbit
One drawback is you can't render the list and the loader at the same time.
Which means whenever doing pagination or refresh, it will always render a
empty list with a loading spinner instead of rendering the current list and a
loading spinner of top.

------
machiaweliczny
A little related to redux data fetching - have any of you tried
[https://github.com/amplitude/redux-query](https://github.com/amplitude/redux-
query) ?

