React Native Offline First with TanStack Query
Patrik
Posted on December 3, 2022
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
npm i @tanstack/react-query
# or
pnpm add @tanstack/react-query
# or
yarn add @tanstack/react-query
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
npx expo install @react-native-community/netinfo
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
.
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.
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:
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.
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:
<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 and for this to work you will need to setup a Supabase project.
Creating a project
- Go to app.supabase.com
- Sign in or Sign up if you haven't already
- Click on "New Project".
- Enter your project details.
- Wait for the new database to launch.
Database
- Go to the SQL Editor page in the Dashboard.
- 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
);
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
.
- Go to the Settings page in the Dashboard.
- Under
General
you will find yourReference ID
(you can also see it in your browsers URL). InSupabase.js
replace<project-reference-id>
with your ownID
. - Go to the Settings -> API page and add your
anon
-key to thesupabaseAnonKey
-variableSupabase.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.
Posted on December 3, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.