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:

Made with Slides.com