Handle GraphQL loading state with React useEffect

Dong Chen
3 min readMar 13, 2020

--

Problem

A great advantage of GraphQL is you can colocate GraphQL query with React components. For example,

Here in the component we get data from GraphQL query (using Apollo query hook), and are immediately able to render it. It is sweet because it is clear what data the component requires from server and how the component renders the data. Colocating the logics makes it much easier to understand and debug the code.

Data fetching takes time, and we usually want to show users some loading indicator before we can render the data. That’s why the query hook also returns a loading variable. Usually in GraphQL tutorials you would see

We show a loading icon during data fetching. This is cool if you have a single component. But if you have more than one component, this code will result in multiple loading spinners on the page, which is obviously less optimal user experience.

Check code here

Another issue above is the page clears old content and replaces it with spinners. This abrupt behavior tends to interrupt user and pull them out of context. I’d rather keep the current content while it’s fetching new data.

Instead, we want something like below: show a single loading spinner and keep the content while fetching new data.

Check code here

Solution

The cause is we are fetching data and handling loading state locally in each component. One solution is to lift up the GraphQL queries and merge them into one big query, and pass down the data to each component as props. This approach will work, but we lose the sweetness of colocating data and component.

A better approach is to have a global loading state and update it from each component. In other words, you need to have a global state that can be accessed and mutated in various levels of components.

React context is perfect for this use case. Let's create a LoadingContext:

Now you can wrap your GraphQL connected components with <LoadingProvider />, which manages a global loading state. And you can use useLoading() to access and mutate the loading state in each component.

The next problem is how to mutate the global loading state in a component?

You can’t do setLoading(loading) directly in a function component, because mutating state is doing a side effect and is prohibited during rendering. Instead, do useEffect which is designed for performing side effects:

Even better, we can extract out this effect and make our custom effect:

Now you can handle loading with one line using the custom effect:

Another cool thing is that it doesn’t have to be “global” in the top app level. You can put the context provider in any level that you want to have a single loading state, and you can even have multiple loading context providers in a page if you want to maintain separate loading states.

Note this approach has a defect if each GraphQL query is sent individually. The loading state would be canceled by the first finished query, which means the loading spinner would disappear as soon as the first query returns data, even though data for other components are still loading. To resolve this, we either have to only mutate state from the last data response (seems complicated 🤔), or make batched GraphQL query (all queries return at the same time then).

Check the complete code here.

If you like this post, don’t forget to give your 👏 (s)! And follow me on twitter!

--

--

Dong Chen
Dong Chen

Written by Dong Chen

Web engineer @robinhood; PhD in Human-Computer Interaction

No responses yet