DEV Community

Cover image for React Native Offline First with TanStack Query
Patrik
Patrik

Posted on

 

React Native Offline First with TanStack Query

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 I'm using Supabase as a backend to fetch and post data. If you need a backend and / or starter code you can follow their Expo Quickstart 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 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

npm i @tanstack/react-query
# or
pnpm add @tanstack/react-query
# or
yarn add @tanstack/react-query
Enter fullscreen mode Exit fullscreen mode

Docs: createAsyncStoragePersister

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
Enter fullscreen mode Exit fullscreen mode

Docs: NetInfo

npx expo install @react-native-community/netinfo
Enter fullscreen mode Exit fullscreen mode

Docs: AsyncStorage

npx expo install @react-native-async-storage/async-storage
Enter fullscreen mode Exit fullscreen mode

If you are using Supabase you might have to use this polyfill and import it in App.js.

npm i react-native-url-polyfill
# or
yarn add react-native-url-polyfill
Enter fullscreen mode Exit fullscreen mode

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.

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

QueryClientProvider

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

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

const asyncPersist = createAsyncStoragePersister({
    storage: AsyncStorage,
    throttleTime: 3000,
  });
Enter fullscreen mode Exit fullscreen mode

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

  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'
    //     });
  }
  });
Enter fullscreen mode Exit fullscreen mode

Finishing up your App.js should return something similar to:

<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>
  );
Enter fullscreen mode Exit fullscreen mode

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 and for this to work you will need to setup a Supabase project.

Creating a project

  1. Go to 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 page in the Dashboard.
  2. Click New query and paste and run the SQL below.
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
);
Enter fullscreen mode Exit fullscreen mode

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 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 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.

Top comments (0)

Regex for lazy developers

regex for lazy devs

You know who you are. Sorry for the callout 😆