5 Reasons why Apollo Client 3 is my top pick for Global State Management in React Apps

image from https://www.apollographql.com/blog/

Regardless of what solution we choose, managing state in a client-side application involves handling storage, update the state/store and notify any components that subscribed to that data of the changes (reactivity).

After having used Redux before for managing my global client state I recently started favouring Apollo Client 3. Here are quick 5 reasons as to why :

  1. Quick and Simple Setup

All we need to do is initiate and configure the cache and the client. The most basic configuration is to just create a new InMemoryCache with the default initial values, an ApolloClient and point it to the GraphQL endpoint as well as passing in the cache. If we only use local data than we can pass just the cache. This is in contrast with Redux where you need to setup reducers, selectors, actions, action creators. Just feels like a lot more setup to get going, especially for small and medium sized projects.
As for data access and operations in Redux we use the useSelector hook for retrieving data from the store and dispatch for modifying it. In Apollo Client we have the useQuery and useMutation for reading and writing data.

A very minimal Apollo Client setup. If we do not use any remote data we can simply omit the URI and just pass in the cache.

2. Reactivity out of the box using reactive variables and cache policies.

These variables can be accessed and modified from anywhere in the application and they re-run every query that depends on them as well as triggering a component re-render each time the data has changed.
These reactive variables are also good containers for storing global state that we need access to throughout the app.

3. Data normalization and automatic updates of local cache (client store).
Normalization is basically an organizing technique that reduces data redundancy. Unlike other solutions where this has to be implemented manually, Apollo automatically normalizes and caches all the data from the query responses. This is done under the hood by splitting the data into individual objects and assigning a unique identifier to each entity, enabling the cache to keep track of it and then storing them as a flat JavaScript object to be easily accessed. By default it uses the id and __typename to compose the unique identifier but in case those are missing from our data we can specify custom fields that we want Apollo to use instead:

example of custom fields used as unique identifiers

Also whenever we modify data on the server Apollo automatically updates the local cache with the updated data as long as we return it in the mutation result :

here we use inline Fragments to return data in the mutation result

This is where normalizing the data is important because this gives Apollo a way to keep track and identify the data that needs to be updated.

Note here that the default behaviour is to replace the old data with the new data but we can also define custom merge functions.

There are few cases where Apollo does not do this automatically but in those cases we can pass an update function to the mutation where we can specify how we would like to update that data in the cache:

custom update function for updating resulting data

4. Easier handling of fetch logic and async states.

Apollo Client comes with all of the logic for handling async state, retry logic and normalizing data build into the client and exposed through the API and all we need to do is declaratively ask for the data we require as well as perform the operations on that data (write, update, delete):

all the async states are exposed through the API

Apollo Client also gives us the possibility to configure the cache policies and by default it will look in the local cache first and then fetch from the server just the data that’s not available in the local cache, optimising the network calls for future requests of the same data.

This is also where normalization of data plays a huge part because it is able to easily identify all the entities in the cache and what data needs to be fetched.

Also we can set default values for the local data when we configure the cache. There we can also modify what the cache returns before the reads and the writes — we can perform client logic on the data before being exposed.

Declaring the read and merge functions to tell the cache what operations to run before reading and writing the data.

5. One solution for both client and server. — Like the line from the Lord of The Rings , “One ring to rule them all”.

Since GraphQL is my go-to solution for exchanging information between the client and server because of the strongly typed schema and intuitive query language, I find it more convenient to use Apollo Client for managing the client data as well instead of trying to store that data in a Redux store for example and have to go through the process of integrating the 2 solutions.

We can even have Apollo include both client and server data fetching in the same query. This is possible using the new field policies and reactive variables in Apollo Client 3. When declaring field policies for the local data — separate from the server schema- we can specify what data to populate them with, either from storage or from a reactive variable. Using the @client tag we can query for data from these local fields only.

Bonus — Quick and easy types generation on the client. We can easily generate types on the client based on the server schema using one of the few tools available. I wrote a more detailed article with a few tips for getting GraphQL Code Generator to work well with React. We can even take this a step further and generate React hooks that are ready to use for fetching and modifying the data — fully typed.

So there you have it, quick 5 reasons why Apollo Client is currently my go-to tool for managing global state in any React application.

Driven by all things creative