Using promises and error boundaries for data loading within your react/redux app.

April 23, 2018 0 Comments

Using promises and error boundaries for data loading within your react/redux app.

 

 

It’s a familiar problem. We have a really nice UI, but it’s waiting for data to load before it’s ready for the user to view. We want to show a temporary Loading component to let the user know that things are working behind the scenes.

It’s a simple enough scenario, but it can present a couple of tricky problems.

Firstly we want to be able to show a loading spinner at any point within the apps hierarchy, regardless of which component is making a data request. This will allow us to show a loader over an entire section of our app if we know that some part of it is still waiting for data.

Secondly we should be able to nest our loaders so that we can show them concurrently. This way, when one component is ready it will display and then if a child of that component isn’t yet ready we show our Loading component again.

A solution that solves both of these problems would be for the component that is waiting for data to signal to our loading component, telling it to display. We don’t want to use redux or an event bus as this would add far too much complexity. Instead, we can use React’s Error Boundaries to take care of this for us.

Error boundaries

With the release of React 16 the componentDidCatch method was added to the React.Component class API. This means that when an error is thrown somewhere within our React component stack, we can catch that error and handle it as we see fit. Here’s our example:

This is great for a few reasons. Firstly we can catch errors at defined points within our app, shielding the rest of the app, which can continue as normal. Secondly as we can choose when to throw and more importantly what to throw. We can handle different types of errors in different ways. Here’s our example again, but with more deterministic error handling.

We can see that instead of throwing an error we instead throw a number, new Number(123). This means that in our ErrorBoundary we can inspect the error instance that is supplied to us. From there we can decide what to do next:

componentDidCatch(error, info) {

// if the error is a number we can do something specific

if(error instanceof Number)
...

Determining when our data has loaded

So now we have a way of trapping errors within our app, and we can also do things specifically for a given type of error. But how is this helpful when trying to solve our loader problem? Well, what we can do is throw a Promise, this will be caught by our error boundary. We can then wait for that promise to resolve, showing our loading spinner. Once the promise has resolved we can then show the components below the ErrorBoundary, our props.children.

First we create a mock data fetcher function that will throw a promise which will resolve after a couple of seconds

const dataFetcher = () => {
throw new Promise((resolve, reject) => {
// fake latency
setTimeout(() => resolve({foo: ‘bar’}), 2000) })
}

Great now we can create a component that will call the dataFetcher on componentDidMount :

class AsyncComponent extends React.Component { 
componentDidMount() {
// we don’t have our data yet, so let’s get it
  if(!this.props.data) 
dataFetcher();
  }
  render () {
return <div>Yay I got my data :) </div>
}
}

Now we need to create our Loader component. We need to first check that the type of error that was thrown was a Promise. Then we need to display a loading state until the promise has resolved. Finally we can pass the resolved data onto our children.

Next we need to wrapper our AsyncComponent within the Loader so that it can receive the data from props:

const App = () => (
<Loader>
<AsyncComponent />
</Loader>
)

Here’s a working example:

This looks great, but there is a problem. Although we’ve resolved the issue of how to show our Loader for any component, no matter where it sits in the apps hierarchy, we are only passing our data back to the first child of the Loader. We want any child of the Loader to receive the data.

So let’s go ahead and solve that issue by creating a component that will make our data available by creating a DataProvider component that will move our data in to React’s context.

Once we have the data in the context, we just need to update our AsyncComponent to use the context to retrieve it’s data.

So now we can place our AsyncComponent anywhere within the app and as long as it appears under the Loader we can fire off the loading spinner and get our data back.

Here’s the final working example:

This will work if we have two requests for the same data e.g

const App = () => (
<div>
<Loader>
<div>
<div>
<AsyncComponent />
</div>
<AsyncComponent />
</div>
</Loader>
</div>
)

It will also work concurrently if we have to separate, nested loaders e.g.

const App = () => (
<div>
<Loader>
<div>
// first this component will load
<AsyncComponent />
        <Loader>
// then this once will load next
<AsyncComponent />
</Loader>
       </div>
</Loader>
</div>
)

Here’s an example that fetches some data from a real API, for a more fully formed example:

What about Redux?

This is all pretty nifty stuff, but it doesn’t have to replace redux or stores or any of the working parts that we normally require for fulfilling async requests and passing data around our apps. Redux is an architectural approach, that provides a clear and simple abstraction between our apps presentation layer and our data model (the store).

Keeping with the redux model, how can we use our loader? Firstly we need to be clear about the stages of our data cycle. That is the point at which it is requested, the point at which it is resolved and the point at which we retrieve it from our store.

With that in mind, let’s say that our app has a router, into which we have added an onEnter method. When we enter a route within our app, the onEnter method is invoked. This method fires an action, which will fetch data for our app and place it into our store.

Next, when we connect to our store, that is the point at which we’d want to either retrieve our data from the store, or if the data hasn’t loaded yet, we would throw our Promise, which in turn would render our loader component. This promise would differ from our previous example, as it would need to check the store to know when our data has loaded.

So how would this look?

Note that this approach makes our Loader much simpler. We don’t need to worry about passing data down via the context any more. We simply need to wait for the promise to resolve and then simply render our children

Here’s a final working example using redux for our data fetching:

Note: Please be aware that this code isn’t ready for production, but it does show how you can approach data loading in a dynamic way within your redux app.

Until next time :)


Tag cloud