When React was released, it solved so many issues about UI building. But there was still a recurring concern, the management of a global state between our different components. So in 2015, the Redux library appears. In the same vein as React, its aim was to make our lives easier. What about almost 4 years later?
A new generation of global state
Meanwhile, another project has become very popular and it comes, once again, from Facebook 👏 This last little one is called GraphQL, and it is a small revolution, it will allow us to advance our application to a higher level.
GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.
GraphQL allows us to receive predictable, normalized and typed data. Thanks to its principles, design the next generation library to manage a global state becomes possible. And Apollo understood this by developing their client library, it offers unprecedented power to synchronize the content of our application with the data of our server.
What React is to the DOM, Apollo is to the state management.
Managing the state with Redux is often repetitive, we end up with the same logic… Apollo makes the procedure more generic and less heavy thanks to the power of GraphQL, while offering new possibilities.
The purpose of this article is not to teach you the basics of GraphQL but rather to show you the power of its use via Apollo Client,
react-apollo in our case.
For this, I will present you 2 versions of a todo list application. A first one which will be simple and quick to write, and a second one more complete and more optimized. Our application should be able to add, complete/incomplete and delete tasks through a GraphQL server. Also, our task list should stay synchronized between each user.
Of course, what we will do here is also possible with Redux but as you will see, the code will be more readable, more maintainable and more accessible. In general, we divide the size of the code by 2 by migrating from Redux to Apollo.
First version — Simple, but functional 💪
As you can see, it’s easy to create a simple todo list application. One query, three mutations and it’s done 🚀
We used fragments to make our components composable and reusable, the wonderful Apollo CLI to strongly types our components and the magical Apollo GraphQL extension for VSCode to enable intellisense in our queries and mutations. Apollo Client provides us some tools to easily and quickly create our application, let’s look at it together.
We want our list of task to stay up-to-date when others users add or remove tasks. A well-known and simple solution to do that is polling 🔁 And
react-apollo supports it out of the box! You just need to set the
pollInterval prop and the query will be refetched regularly, every 10 seconds in our example.
When the user removes a task, a deleting mutation is initiated, and if it succeeds, we have to inform the user by removing the task of the list. We need to update the data of your previous queries, i.e. update the cache!
If you perform a mutation on an item already stored in the cache, Apollo Client is able to find it and to apply the update in the cache. In order to identify an item, Apollo just needs to know its
__typename and its
id thanks to the data normalization.
So, if you request the
id field in your mutation response,
__typename being automatically added by the library, you will not need to handle the update of your primary query. It will be updated for you 🙌
However, if you want to add or delete an item, Apollo Client cannot manage it on its own. But you can update the data in your cache extremely easily by using the
refetchQueries prop in the
Because refetched queries are handled asynchronously, setting
awaitRefetchQueries to true ensure they will be completed before the mutation is resolved (that means before the
loading value becomes false).
Voilà, it’s done! With only two lines of code, the cache of the
GetTasks query will be updated after each task deletion. Simple, isn’t it?
In a few lines and in a very short time, we were able to create a todo list application. It’s great but not enough, we encounter two main issues:
- Our UX is not fluid, we have a lot of loading everywhere. Even though, we can determine the next state of our application, we shouldn’t have to wait the server response.
- The number of our requests is not optimized, 1 request every 10 seconds (polling) and 2 requests are necessary to delete or add a task.
- We need to wait 10 seconds to see the update of another user. And it’s not conceivable to reduce the delay of the polling. We will overload our server.
Second version — When the magic happens ✨
To improve the user experience and reduce the number of requests to our server, we will use some advanced features offered by Apollo.
Update Cache & OptimisticUI
To make our application more fluid, we will remove the second request to keep the cache synchronized and determine the result without having to wait the server response. To fulfill this goal, we will update the cache manually and use the
optimisticResponse prop provided by the
Mutation component. For the latter, you have to write the presumed value of the mutation response.
To manually update our cache, we have to write a function into the
update prop. This function can be triggered on 2 occasions: if the mutation is a success, or just before sending the request to the server when you provide an
optimisticResponse prop. So be careful, this function could be triggered multiple time. Our function is composed of 3 steps:
- Read the current data of the query from the cache
- Update the data
- Write the new data of the query into the cache
I recommend you to use the immutability principle when you update your query data. It’s a good practice to have when you deal with data and React components.
They are several ways to update the cache, you can read more about how to do that in this article.
But these optimizations come with another problem … our experience to complete/incomplete tasks is so fluid the user can initiate a lot of requests in same time. And we are not sure these requests will be resolved in the good order, our optimization could introduce some bugs 😱
When we complete/incomplete a task quickly several times is not necessary to send all these requests, only the last one is important. Maybe this notion remembers you something … debounce 😄
To debounce our requests, we will use another aspect of Apollo named Links. It allows us to customize the control flow of GraphQL requests.
Here, the debounce link will use the context of each request to determine if it has to be debounced or not. We need to set the
context prop of the
Mutation component to pass these information. Requests which share the same
debounceKey will be debounced together.
Finally, to keep our list of tasks updated without making a request every 10 seconds and to avoid waiting to see an update from another user, we will use web-sockets. Once again, Apollo provide us the fitting feature: Subscription.
We want to update our
GET_TASKS query when a subscription is triggered, so we just need to use the
subscribeToMore function provided by the
Query component. This function needs the document to watch and a callback returning the updated data of our query according to the received subscription. That’s all!
As in the mutation component, the update function is only necessary when deleting and adding items to a list. If the subscription document returns the
id of the item to update, Apollo will be able to update the cache on its own thanks to the normalization.
Apollo and the community provide some other tools to take easily your application to the next level, don’t hesitate to glance these packages.
Compose your link
Batch multiple operations into a single HTTP request
Attempt an operation multiple times if it fails due to network or server errors.
Local state management
We’ve learned how to manage remote data from our GraphQL server with Apollo Client, but what should we do with our local data? We want to be able to access boolean flags and device API results from multiple components in our app. Ideally, we would like the Apollo cache to be the single source of truth for all data in our client application.
Defer (experimental feature)
Many applications that use Apollo fetch data from a variety of microservices, which may each have varying latencies and cache characteristics. Apollo comes with a built-in directive for deferring parts of your GraphQL query in a declarative way, so that fields that take a long time to resolve do not need to slow down your entire query.
As you can see, Apollo allows us to concentrate on the essential. In few lines, we write a todo list with a fluid experience.
When we use Redux, we feel like we always write the same things, always the same logic… Its rival permits us to write DRY code and to not reinvent the wheel. Apollo Client, this is not magic, but it makes your development experience magic.
The saved time allows us to do more complex things like Optimistic-UI and Real-Time in order to upgrade the user experience of our application. Apollo offers us a new vision of state management. If you want to learn more about the staggering work made by the Apollo team to make our application great again, I advise you to go read their exhaustive docs 💪 Have fun!
At PayFit, the Back-Office application is developed by the Internal-Tools team I am leading. We use
react-apollo since 1 year now and it’s gorgeous, our base code has been cleaned and reduced, we have moved some logic in the server side, and we develop features more quickly 🚀