Offline React Query
— ReactJs, React Query, JavaScript, TypeScript — 5 min read
- #1: Practical React Query
- #2: React Query Data Transformations
- #3: React Query Render Optimizations
- #4: Status Checks in React Query
- #5: Testing React Query
- #6: React Query and TypeScript
- #7: Using WebSockets with React Query
- #8: Effective React Query Keys
- #8a: Leveraging the Query Function Context
- #9: Placeholder and Initial Data in React Query
- #10: React Query as a State Manager
- #11: React Query Error Handling
- #12: Mastering Mutations in React Query
- #13: Offline React Query
- #14: React Query and Forms
- #15: React Query FAQs
- #16: React Query meets React Router
- #17: Seeding the Query Cache
- #18: Inside React Query
- #19: Type-safe React Query
- #20: You Might Not Need React Query
- #21: Thinking in React Query
- #22: React Query and React Context
- #23: Why You Want React Query
- #24: The Query Options API
- #25: Automatic Query Invalidation after Mutations
- #26: How Infinite Queries work
I've said it time and time again - React Query is an async state manager. As long as you give it a Promise, resolved or rejected, the library is happy. Doesn't matter where that Promise comes from.
There are many ways to produce promises, but by far the biggest use-case is data fetching. Very often, that requires an active network connection. But sometimes, especially on mobile devices where the network connection can be unreliable, you need your app to also work without it.
Issues in v3
React Query is very well-equipped to handle offline scenarios. Because it provides a caching layer, as long as the cache is filled, you can keep working even if you don't have a network connection. Let's look at a couple of edge-case scenarios where v3 will not work as expected. I will use our basic post list / post detail example from the docs for illustration:
1) no data in the cache
As I said, in v3, things work well as long as the cache is filled. An edge case scenario where things get weird would be the following:
- You have a good network connection and navigate to the list view
- You lose connection and click on a post.
What happens is that your query will stay in loading
state until you regain connection. Also, you can see a failed network request in the browser devtools. That is because React Query will always fire off the first request, and if that fails, it will pause retries if you have no network connection.
Further, the React Query Devtools will show that your query is fetching
, which is not entirely true. The query is actually paused
, but we have no concept to represent that state - it's a hidden implementation detail.
2) no retries
Similarly, if you have turned off retries altogether in the above scenario, your query will go to error state immediately, with no way of stopping that.
Why do I need retries
for my query to pause
if I have no network connection 🤷♂️?
3) queries that don't need the network
Queries that don't need a network connection to work (e.g. because they do an expensive async processing in a web worker) will be paused until you regain network connection if they fail for some other reason. Also, those queries won't run on window focus because that feature is completely disabled if you have no network connection.
In summary, there are two major issues: In some cases, React Query assumes that network connection is needed when that might not be true (case 3), and in other cases, React Query fires off a query even though it probably shouldn't (cases 1 and 2).
The new NetworkMode
In v4, we've tried to tackle this problem holistically with a new networkMode
setting. With this, we can clearly distinguish between online and offline queries. It is an option for useQuery
as well as useMutation
, which means you can set it globally or on a per-query basis. After all, you might have some queries that need network connection, and some that don't.
online
This is the new default mode in v4, as we expect most users to use React Query in combination with data fetching. In short, with this setting, we assume that a query can only run if it has an active network connection.
So what happens if you want to run a query that needs network connection when you don't have one? The query will go to a new paused
state. That paused
state is secondary to the main state that the query can be in: loading
, success
or error
, because you can lose your network connection at any time.
This means you can be in success
state and paused
, for example, if you've fetched data successfully once but a background refetch got paused.
Or, you can be in loading
state and paused
if a query mounts for the first time.
fetchStatus
We've always had the isFetching
flag that indicated that a query was running. Similar to the new paused
state, a query could be success
and fetching
, or it could be error
and fetching
. Background refetches give you a lot of possible states to be in (👋 state machines).
As fetching
and paused
are mutually exclusive, we've combined them into the new fetchStatus
that now gets returned from useQuery
:
fetching
: The query is really executing - a request is in-flight.paused
: The query is not executing - it is paused until you have regained your connection.idle
: The query is currently not running.
As a rule of thumb, the status
of the query will give you information about the data
: success
means you'll always have data, loading
means you don't have data yet.
On the other hand, the fetchStatus
gives you information about the queryFn
: is it running or not? The isFetching
and isPaused
flags are derived from that status.
Let's take a look at how case 1 from above can look like in v4. Please notice the new network mode toggle button in the RQ devtools. It's pretty cool because it doesn't actually turn off your network - it just makes React Query believe that there is no network for testing purposes. Yes, I am quite proud of it. 😊
We can clearly see the state the query is in (paused
) due to the new purple status badge. Also, the first network request is made once we turn the network back on.
always
In this mode, React Query does not care about your network connection at all. Queries will always fire, and they will never be paused. This is most useful if you use React Query for something other than data fetching.
offlineFirst
This mode is very similar to how React Query worked in v3. The first request will always be made, and if that fails, retries will be paused. This mode is useful if you're using an additional caching layer like the browser cache on top of React Query.
Let's take the GitHub repo API as an example. It sends the following response headers:
1cache-control: public, max-age=60, s-maxage=60
which means that for the next 60 seconds, if you request that resource again, the response will come from the browser cache. The neat thing about this is that it works while you're offline, too! Service workers, e.g. for offline first PWAs, work in a similar way by intercepting the network request and delivering cached responses if they are available.
Now those things wouldn't work if React Query would decide to not fire the request because you have no network connection, like the default online
mode does. To intercept a fetch request, it must happen :) So if you have this additional cache layer, make sure to use networkMode: 'offlineFirst'
.
If the first request goes out, and you hit the cache - great, your query will go to success
state, and you'll get that data. And if you have a cache miss, you'll likely get a network error, after which React Query will pause the retries, which will put your query into the paused
state. It's the best of both worlds. 🙌
What does all of this mean for me, exactly?
Nothing, unless you want to. You can still decide to ignore that new fetchStatus
and only check for isLoading
- React Query will behave just like before (well - case 2 from above will even work better because you won't see the network error).
However, if making your app robust for situations where you have no network connection is a priority for you, you now have the option to react to the exposed fetchStatus
and act accordingly.
What you do with that new status is up to you. I'm excited to see which ux people will build on top of this. 🚀
That's it for today. Feel free to reach out to me on twitter if you have any questions, or just leave a comment below. ⬇️