📜 ⬆️ ⬇️

GraphQL offline queries with Redux Offline and Apollo

It's funny, but with the proliferation of the Internet in the world, web applications that work without connecting to the network are increasingly in demand. Users (and customers) want functional Internet applications in online, offline and in areas with intermittent connectivity.


And this ... is not easy.


Let's see how to create an effective solution that works without a network connection on React and a GraphQL data layer using the Apollo Client . The article is divided into two parts. This week we will analyze offline requests. Next week, let's start mutations.


Redux Persist and Redux Offline


Behind the Apollo Client is the same Redux . And that means the whole Redux ecosystem with numerous tools and libraries is available in applications on Apollo.


Redux has two main players offline support: Redux Persist and Redux Offline.


Redux Persist is an excellent, but only basic, tool for loading redux storage into localStorage and restoring it back (other storage locations are also supported). According to accepted terminology, recovery is also called rehydration.


Redux Offline extends Redux Persist with an extra layer of functions and utilities. Redux Offline automatically learns about disconnections and reestablishments of the connection and, when disconnected, allows you to queue actions and operations, and when you restore, it automatically replays these actions again.


Redux Offline is a “charged” option for working without a network connection.


Offline requests


The Apollo Client does a good job with short-term interruptions in network connections. When a request is made, its result is placed in its own Apollo repository.


If the same request is made again, the result is immediately taken from the repository on the client and given to the requesting component, unless the fetchPolicy parameter is set to network-only . This means that if there is no network connection or the server is not available, repeated requests will return the last saved result.


However, it is necessary for the user to close the application, and the repository is lost. How not to lose Apollo storage on the client between application restarts?


Redux Offline comes to the rescue.


Apollo keeps its data in the Redux repository (in the apollo key). localStorage recording the entire repository in localStorage and restoring the next time you start the application, you can transfer the results of past requests between sessions of work with the application, even if you are not connected to the Internet .


Using Redux Offline and Apollo Client is complete without nuances. Let's see how to make both libraries work together.


Creating a repository manually


Typically, the Apollo client is created quite simply:


 export const client = new ApolloClient({ networkInterface }); 

The ApolloClient constructor automatically creates the Apollo repository (and indirectly the Redux repository). The resulting client instance is served in the ApolloProvider component:


 ReactDOM.render( <ApolloProvider client={client}> <App /> </ApolloProvider>, document.getElementById('root') ); 

When using Redux Offline, you must manually create a Redux repository. This allows you to connect to the repository middleware (middleware) from Redux Offline. To start, just repeat what Apollo does:


 export const store = createStore( combineReducers({ apollo: client.reducer() }), undefined, applyMiddleware(client.middleware()) ); 

Here, the store uses a reducer and an intermediate processor (middleware) from an Apollo instance ( client variable), and the initial state is undefined .


Now you can submit the store to the ApolloProvider component:


 <ApolloProvider client={client} store={store}> <App /> </ApolloProvider> 

Excellent. Creating a Redux repository is under control, and you can continue with Redux Offline.


Basics of Persistent Query Storage


In the simplest case, adding Redux Offline is to add another intermediate handler to the repository.


 import { offline } from 'redux-offline'; import config from 'redux-offline/lib/defaults'; export const store = createStore( ... compose( applyMiddleware(client.middleware()), offline(config) ) ); 

Without additional settings, the offline handler will automatically begin to write the Redux vault to localStorage .


Do not believe?


Open the console and get this entry from localStorage :


 localStorage.getItem("reduxPersist:apollo"); 

A JSON large object is displayed, representing the complete current state of the Apollo application.



Sumptuously!


Now Redux Offline automatically takes snapshots of Redux's storage and writes them to localStorage . Each time you start the application, the saved state is automatically taken from localStorage and restored to the Redux storage.


All queries whose results are in this repository will be returned data even in the absence of a connection to the server.


Competition in repository recovery


Alas, the recovery of the repository is not instantaneous. If the application makes a request while Redux Offline restores the vault, Strange Things (tm) may occur.


If in Redux Offline you enable logging for autoRehydrate mode (which in itself makes you nervous), when you first load the application, you can see errors like:


 21 actions were fired before rehydration completed. This can be a symptom of a race condition where the rehydrate action may overwrite the previously affected state. Consider running these actions after rehydration: …  21     .    , -         .       : … 

The developer Redux Persist acknowledged the problem and suggested a recipe for deferred rendering of the application after recovery. Unfortunately, his decision is based on the manual invocation of the persistStore . However, Redux Offline makes such a call automatically.


Let's look at another solution.


Create a Redux action with the name REHYDRATE_STORE , as well as the corresponding reducer, which sets the value true for the rehydrated attribute in the Redux repository:


 export const REHYDRATE_STORE = 'REHYDRATE_STORE'; export default (state = false, action) => { switch (action.type) { case REHYDRATE_STORE: return true; default: return state; } }; 

Connect the created reducer to the storage and configure Redux Offline so that at the end of the recovery a new action is performed.


 export const store = createStore( combineReducers({ rehydrate: RehydrateReducer, apollo: client.reducer() }), ..., compose( ... offline({ ...config, persistCallback: () => { store.dispatch({ type: REHYDRATE_STORE }); }, persistOptions: { blacklist: ['rehydrate'] } }) ) ); 

Excellent! When Redux Offline restores the repository, it will call the persistCallback function, which will trigger the REHYDRATE_STORE action and eventually set the rehydrate sign in the repository.


Adding rehydrate to the blacklist array ensures that this part of the repository will not be written to and recovered from localStorage .


Now that the repository has information about the end of the recovery, we will develop a component that reacts to changes in the rehydrate field and visualizes the child components only if rehydrate is true :


 class Rehydrated extends Component { render() { return ( <div className="rehydrated"> {this.props.rehydrated ? this.props.children : <Loader />} </div> ); } } export default connect(state => { return { rehydrate: state.rehydrate }; })(Rehydrate); 

Finally, we will place the <App /> component inside the <Rehydrated /> component to prevent the output of the application before the end of the rehydration:


 <ApolloProvider client={client} store={store}> <Rehydrated> <App /> </Rehydrated> </ApolloProvider> 

Uff.


Now, the application will blithely wait for Redux Offline to restore the storage from localStorage , and only then continue drawing and will do all subsequent queries or mutations in GraphQL.


Oddities and explanations


There are a few oddities and things that require clarification when using Redux Offline with the Apollo client.


First, it should be noted that the examples in this article use version 1.9.0-0 of the apollo-client package. For the Apollo Client version 1.9, there are some corrections of some strange manifestations when working with Redux Offline .


Another oddity of this couple is that Redux Offline doesn't seem to get along well with the Apollo Client Devtools . Attempts to use Redux Offline with Devtools installed sometimes lead to unexpected and seemingly incoherent errors.


Such errors can be easily eliminated if you do not connect it to Devtools when creating the Apollo client .


 export const client = new ApolloClient({ networkInterface, connectToDevTools: false }); 

Keep in touch


Redux Offline gives the Apollo application the basic mechanisms for executing requests even when the application is restarted after disconnecting from the server.


A week later, immersed in the processing of offline mutations using Redux Offline.


Be in touch!


Translation of the article . Original author Pete Corey.


')

Source: https://habr.com/ru/post/335210/


All Articles