Going Offline-First in React Native with GraphQL
Kadi Kraman
Who am I?
Software Engineer at
@kadikraman
working as a software engineer
6 years
building things with Node.js / React
4 years
using React Native in production
1.5 years
Open source maintainer and contributor
Offline in an
"always online" world...
A note about tooling
React Native
Apollo 2 (GraphQL)
I will be using...
{
query posts {
posts {
id
createdAt
text
}
}
}
{
query posts {
posts {
id
createdAt
text
}
}
}
Request:
GET <host>/api/v1/posts
Example result:
HTTP code 200
[
{
id: 1,
createdAt: '2015-03-25T12:00:00Z',
text: 'First post'
},
{
id: 2,
createdAt: '2015-03-25T12:00:00Z',
text: 'Second post'
}
]
RESTful equivalent:
GraphQL query:
{
mutation createPost($text: String!) {
createPost(text: $text) {
id
text
createdAt
}
}
}
{
mutation createPost($text: String!) {
createPost(text: $text) {
id
text
createdAt
}
}
}
Request:
POST <host>/api/v1/posts
-H 'Content-Type: application/json'
-d { text: 'Some text' }
Example result:
HTTP code 201
{
id: 2,
text: 'Some text',
createdAt: '2015-03-25T12:00:00Z'
}
RESTful equivalent:
GraphQL query:
{
mutation deletePost($id: ID!) {
deletePost(id: $id) {
id
}
}
}
{
mutation deletePost($id: ID!) {
deletePost(id: $id) {
id
}
}
}
Request:
DELETE <host>/api/v1/posts/1
Example result:
HTTP code 204
{ id: 1 }
RESTful equivalent:
GraphQL query:
Now let's make this work
OFFLINE
Online 😊
Offline 😭
How?
Cache and persist the data and read from cache when offline
Apollo Cache Persist
https://github.com/apollographql/apollo-cache-persist
import { AsyncStorage } from 'react-native';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { persistCache } from 'apollo-cache-persist';
const cache = new InMemoryCache({...});
await persistCache({
cache,
storage: AsyncStorage,
});
// Continue setting up Apollo as usual.
const client = new ApolloClient({
cache,
...
});
NB! Ensure persistCache resolves before loading your app!
Online 😊
Offline 😍
fetchPolicy: 'cache-and-network'
{
mutation createPost($text: String!) {
createPost(text: $text) {
id
text
createdAt
}
}
}
What happens when we create a new post?
OPTION 1: by refetching the posts list after the mutation
How?
It gets added to the posts list on the main screen
OPTION 2: optimistic update
Figure out what you THINK the resulting data will look like
export const getOptimisticResponse = (text: string) => ({
__typename: 'Mutation',
createPost: {
__typename: 'Post',
id: String(new Date().getTime()),
text,
createdAt: new Date()
}
});
Add the "optimistic" response to your cache
import gql from 'graphql-tag';
const GET_POSTS = gql`
query posts {
posts {
id
createdAt
text
}
}
`;
export const createPostUpdate = (cache, response) => {
const cachedPosts = cache.readQuery({
query: GET_POSTS
});
cache.writeQuery({
query: GET_POSTS,
data: {
posts: [response.data.createPost, ...cachedPosts.posts]
}
});
};
Put it all together
Success?
We still need to handle the network request failed
Apollo Link Retry
https://github.com/apollographql/apollo-link/tree/master/packages/apollo-link-retry
Success!
We're online even when we're really offline
Main takeaways
To make your app work offline:
1. Cache and persist your data
2. Always write optimistic updates for mutations
3. Retry applicable mutations
Do this as you go and you're essentially offline first!
https://github.com/kadikraman/offline-first-mobile-example
My sample app from the screenshots:
Thank you!
https://slides.com/kadikraman/offline-first/fullscreen
These slides:
Going Offline-First with GraphQL
By Kadi Kraman
Going Offline-First with GraphQL
GraphQL Finland 2018 & React Native London, October 2018
- 4,862