React Native Offline First with TanStack Query
Tags: reactnative, reactquery, supabase, frontend
Published: 2022-12-03 12:07:54

It's very important to give your users a good in-app experience even if their app can't connect to the internet. In this post I will show you how to persist your API calls / mutations when the device is offline and keep your app and backend in sync by storing the mutations and retrigger when the device reconnects to the internet.

For this [example](https://github.com/fedorish/example-react-native-offline-tanstack-query) I'm using [Supabase](https://supabase.com/) as a backend to fetch and post data. If you need a backend and / or starter code you can follow their [Expo Quickstart](https://supabase.com/docs/guides/with-expo) to create an application that's hooked up to a backend in just minutes. 

This post will be split into two sections, one where I will try and explain what you need to add to your own app to get it working. The other section will show you how to use the [example app on GitHub](https://github.com/fedorish/example-react-native-offline-tanstack-query) I've provided. 

# Adding it to your existing app

I would recommend that you look through my example on GitHub to see a working demo and that you read the documentation. I will not go into TanStack Query details such as how to query data.

We'll start off with installing the packages we need.

### Installing packages

[Docs: TanStack Query](https://tanstack.com/query/v4/docs/installation)
```bash
npm i @tanstack/react-query
# or
pnpm add @tanstack/react-query
# or
yarn add @tanstack/react-query
```

[Docs: createAsyncStoragePersister](https://tanstack.com/query/v4/docs/plugins/createAsyncStoragePersister)
```bash
npm install @tanstack/query-async-storage-persister @tanstack/react-query-persist-client
# or 
pnpm add @tanstack/query-async-storage-persister @tanstack/react-query-persist-client
# or
yarn add @tanstack/query-async-storage-persister @tanstack/react-query-persist-client
```

[Docs: NetInfo](https://docs.expo.dev/versions/latest/sdk/netinfo/)
```bash
npx expo install @react-native-community/netinfo
```
[Docs: AsyncStorage](https://docs.expo.dev/versions/latest/sdk/async-storage/)
```bash 
npx expo install @react-native-async-storage/async-storage
```

If you are using Supabase you might have to use this polyfill and import it in `App.js`.
```bash
npm i react-native-url-polyfill
# or
yarn add react-native-url-polyfill
```


### Network status 
First thing we'll add is an `EventListener` that listens to network changes. In the browser TanStack Query handles this automatically, but on mobile we need to set it up ourselves. If you are running this on an iPhone simulator you might experience issues, _it seems like the iPhone simulators have some issues reconnecting properly. I have not experience this with Android simulators._

```jsx
React.useEffect(() => {
    if (Platform.OS !== 'web') {
      return NetInfo.addEventListener((state) => {
        const status =
          state.isConnected != null &&
          state.isConnected &&
          Boolean(state.isInternetReachable);
        onlineManager.setOnline(status);
      });
    }
  }, []);
```

### QueryClientProvider

Instead of using the normal `QueryClientProvider` we will use `PersistQueryClientProvider`. The configuration we will use is the following:

```jsx
const queryClient = new QueryClient({
   defaultOptions: {
      mutations: {
        staleTime: Infinity,
        cacheTime: Infinity,
        retry: 0,
      },
    },
  });

const asyncPersist = createAsyncStoragePersister({
    storage: AsyncStorage,
    throttleTime: 3000,
  });
```

We also need to configure mutationDefaults which will run when the mutations we specify are triggered. 

```jsx
  queryClient.setMutationDefaults(['yourKey'], {
    // mutationFn will give you access to
    // any variables passed to the mutation
    mutationFn: ({ variable_1, variable_2 }) => {
    // Your API call (should return a promise)
    // return axios.post('/user', {
    //          firstName: 'Fred',
    //          lastName: 'Flintstone'
    //     });
  }
  });
```

Finishing up your `App.js` should return something similar to:
```jsx
<PersistQueryClientProvider
      client={queryClient}
      persistOptions={{
        persister: asyncPersist,
      }}
      // onSuccess will be called when the initial restore is finished
      // resumePausedMutations will trigger any paused mutations
      // that was initially triggered when the device was offline
      onSuccess={() => queryClient.resumePausedMutations()}
    >
        <YourApp />

    </PersistQueryClientProvider>
  );
```

This should be enough to get up and running, but depending on your project you might have to update certain options to work in your app. 

# Example using Supabase
Sometimes it can helpful to play around with a working example yourself. I've setup basic example that you can play around with. You can find the [repo on github](https://github.com/fedorish/example-react-native-offline-tanstack-query) and for this to work you will need to setup a [Supabase](https://supabase.com/) project.

### Creating a project
1. Go to [app.supabase.com](app.supabase.com)
2. Sign in or Sign up if you haven't already
3. Click on "New Project".
4. Enter your project details.
5. Wait for the new database to launch.

### Database 
1. Go to the [SQL Editor](https://app.supabase.com/project/_/sql) page in the Dashboard.
2. Click `New query` and paste and run the SQL below.
```sql
create table todos (
  id bigint generated by default as identity primary key,
  inserted_at timestamp with time zone default timezone('utc'::text, now()) not null,
  updated_at timestamp with time zone default timezone('utc'::text, now()) not null,
  todo text
);
```

### Add API Keys
Before we can run the application we need to connect our app to the Supabase project by adding our projects `Reference ID` and `anon`-key to `Supabase.js`. 
1. Go to the [Settings](https://app.supabase.com/project/_/settings) page in the Dashboard.
2. Under `General` you will find your `Reference ID` (you can also see it in your browsers URL). In  `Supabase.js` replace `<project-reference-id>` with your own `ID`.
3. Go to the [Settings -> API](https://app.supabase.com/project/_/settings/api) page and add your `anon`-key to the `supabaseAnonKey`-variable `Supabase.js`.

Now you are ready to launch the app and test it out. I've had some issues with both Android and iPhone simulators, such as not reconnecting to internet correctly and not persisting mutations correctly. So I would recommend testing this on a physical device. 

Hopefully this helps someone, let me know if you have any questions or find any errors I've made.