How to use React Native in offline mode (Part 1 — setup Apollo client)

sushil bansal
3 min readApr 23, 2019

I recently implemented offline functionality in my React Native app and followed a process which is straightforward and not that complex but little lengthy. It has got its own flaws which i will explain in the end. So let’s start with Apollo Setup

1. Apollo setup (using apollo 2):

import { InMemoryCache, NormalizedCacheObject } from "apollo-cache-inmemory";                       
import { persistCache } from "apollo-cache-persist"; import { PersistedData, PersistentStorage } from "apollo-cache-persist/types";
import { ApolloClient } from "apollo-client"; import { ApolloLink } from "apollo-link"; import { onError } from "apollo-link-error"; import { HttpLink } from "apollo-link-http"; import { RetryLink } from "apollo-link-retry"; import { WebSocketLink } from "apollo-link-ws"; import { getMainDefinition } from "apollo-utilities"; import { AsyncStorage } from "react-native";
const host = "YOUR_SERVER_ADDRESS";
const wshost = "wss://YOUR_SERVER_ADDRESS";
const httpLink = new HttpLink({
uri: host,
credentials: "include"
});
// Create a WebSocket link:
const wsLink = new WebSocketLink({
uri: wshost,
options: {
reconnect: true
}
});
const errorLink = onError(({ response, graphQLErrors, networkError }) => {
if (graphQLErrors) {
console.log(graphQLErrors);
graphQLErrors.map(({ message, locations, path }) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
)
);
}

if (networkError) console.log(`[Network error]: ${networkError}`);
console.log("response", response);
if (response) {
response.errors = null;
}
});
// using the ability to split links, you can send data to each link
// depending on what kind of operation is being sent
const link = new RetryLink({ attempts: { max: Infinity } }).split(
// split based on operation type
({ query }) => {
const { kind, operation } = getMainDefinition(query) as any;
return kind === "OperationDefinition" && operation === "subscription";
},
wsLink,
httpLink
);

const cache = new InMemoryCache();
export const waitOnCache = persistCache({
cache,
storage: AsyncStorage as PersistentStorage<
PersistedData<NormalizedCacheObject>
>,
maxSize: false,
debug: true
});

export const client = new ApolloClient({
link: ApolloLink.from([errorLink, link]),
cache
});

Error Link: check how i am capturing the errors (using apollo-link-error) and then logging those errors. I am doing nothing after capturing the error (esp for Network error). You may add your logger to log the error but make sure that you are not terminating the process when you get the Network error.

Reason is if you are offline and try to perform an action then you will get network error. If you are using default error handling then application will stop there. So I am using my custom error handling which is ignoring the network error and let the app continue.

const errorLink = onError(({ response, graphQLErrors, networkError }) => {
if (graphQLErrors) {
console.log(graphQLErrors);
graphQLErrors.map(({ message, locations, path }) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
)
);
}

if (networkError) console.log(`[Network error]: ${networkError}`);
console.log("response", response);
if (response) {
response.errors = null;
}
});

Retry Link: i am using apollo-link-retry. What it does is it will try to execute the operation (in case of server or network error). You can read the documentation for more info: https://www.apollographql.com/docs/link/links/retry

I am splitting the links based on operations. It is optional and not really required.

const link = new RetryLink({ attempts: { max: Infinity } }).split(
// split based on operation type
({ query }) => {
const { kind, operation } = getMainDefinition(query) as any;
return kind === "OperationDefinition" && operation === "subscription";
},
wsLink,
httpLink
);

Cache: I am using apollo-cache-persist. It will store the data whenever you visit any page or perform an action in your app and store that data in AsyncStore (in case of RN. There are lot more options; check out the documentation). Next step is to retrieve the data when app loads (in your main component).

const cache = new InMemoryCache();export const waitOnCache = persistCache({
cache,
storage: AsyncStorage as PersistentStorage<
PersistedData<NormalizedCacheObject>
>,
maxSize: false,
debug: true
});

i am exporting this persistCache which i will use in my main component (App.tsx) like below:

async componentDidMount() {
try {
await waitOnCache;
} catch (error) {
console.error("Error restoring Apollo cache", error);
} finally {
console.log("finally cache is restored");
}
}

End of Part 1. Please let me know in comments if you have implemented in a different way or if you have any questions.

--

--