Offline React Query
- #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
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.
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:
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.
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 🤷♂️?
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).
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.
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:
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.
We've always had the
isFetching flag that indicated that a query was running. Similar to the new
paused state, a query could be
fetching, or it could be
fetching. Background refetches give you a lot of possible states to be in (👋 state machines).
paused are mutually exclusive, we've combined them into the new
fetchStatus that now gets returned from
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
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
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.
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.
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
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. 🙌
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. ⬇️