React-query: Shared cache between browser tabs?

Created on 9 Apr 2020  路  7Comments  路  Source: tannerlinsley/react-query

I'm sorry for the (most probably) noob question, but I wasn't sure, so here I am.

Say that I have my app open in 2 browser tabs. In one of the tabs I do a mutation and I optimistically update the value in the query cache, like in the example found in the documentation:

useMutation(updateTodo, {
  // When mutate is called:
  onMutate: (newTodo) => {
    // Snapshot the previous value
    const previousTodos = queryCache.getQueryData('todos')

    // Optimistically update to the new value
    queryCache.setQueryData('todos', old => [...old, newTodo])

    // Return the snapshotted value
    return () => queryCache.setQueryData('todos', previousTodos)
  },
  // If the mutation fails, use the value returned from onMutate to roll back
  onError: (err, newTodo, rollback) => rollback
  // Always refetch after error or success:
  onSettled: () => {
    queryCache.refetchQueries('todos')
  }
})

Currently when I switch tabs my data will synchronize and re-fetch once I focus on a tab/window, but there is a slight delay since we're waiting for the refetch to be done.

Is there a way to read the data from cache first and have the refetch be done in the background. So that when I switch tabs the new data will be immediately available, before the refetch is complete. I'm not sure if the cache is shared between tabs, or if I have a misconception of how things work exactly.

I hope my question makes sense, and let me know if you need clarifications.

P.S. - I love this library, good job everyone.

Most helpful comment

@kamranayub I would love to see your implementation before you get to deep. Maybe I could offer a first-class integration point for this?

All 7 comments

AFAIK the cache is per tab because it's in-memory, so it is sandboxed to the tab by Chrome.

My team may be implementing a mechanism to allow "persistent" caching through local storage but we will likely build it on top of react-query. If we do and it's in a good state to share, I'll be happy to contribute it or as a separate "add-on" to react-query.

The way you'd likely go about this is by wrapping the useQuery hooks and adding callbacks to store the successful results in local storage and then in the initialData callback, attempt to read from the cache. Perhaps you would also need to force the query into manual mode, then write an effect on mount that would try to check the persisted cache and call refetch if it's not available (falling back to normal operation). This is similar to how we are also building in SSR hydration to useQuery as well.

Maybe you can give this a try https://developer.mozilla.org/en-US/docs/Web/API/Window/storage_event

onMutate () {
  // your code
  window.localStorage.setItem('someKey', updatedState)
}

// on the other tab
window.addEventListener('storage', () => {
 // setQueryData
});

or

onSuccess() {
  // your code
  window.localStorage.setItem('timestamp', Date.now())
}

// on the other tab
window.addEventListener('storage', () => {
 // refetch
});

I think I had a misconception that the cache is for some reason already in local storage, I read a bit more and I see that it's in memory (and it makes sense).

Thanks for all the help folks.

@kamranayub I would love to see your implementation before you get to deep. Maybe I could offer a first-class integration point for this?

Any news about an in-storage persistence? I Am considering using react query for a react native app with offline abilities, and will build some basic system to do so on top of RQ. I am interested in any prior attempt. @tannerlinsley @kamranayub

At the moment nothing formal. At work, we use IndexedDB / local storage but it happens within the queryFn.

I also made a helper that can look at the cache-control header of a response and we can then set the query stale time dynamically to match the max-age value. Those are the only two approaches we use at the moment.

In a sample app I am building, I use service worker cache-first strategy along with react query to add offline support. But that's manual through Workbox.

We are also looking to implement some localStorage syncing mechanism for react-query.
It makes a lot of sense when you take UX into consideration.

Consider the following enhanced Todo application.
The main page will have a list of todo titles. When pressing on a todo item, the todo will open in a modal with all of the todo details.
UX-wise, if this todo is fetched from the cache and no changes were made, then there are no problems.
If the todo is fetched from the cache and some changes were made from the background fetch, then a message appears to the user: "Changes were made to this todo. Press here to reload the todo".

Now imagine a user opening a todo in a new tab and editing it.
When he goes back to his previous tab and views that todo he will get this annoying message, that warns him of an interaction that he himself did.
This entire inconvenience could have been avoided if the cache was shared between the tabs.

I know that it's possible to implement the localStorage sync manually by wrapping/decorating the different hooks, but I believe that this kind of behavior makes sense to be part of the package itself and configurable.

WDYT? @tannerlinsley

P.S: Also this can be similar for sessionStorage, and if you do one you can easily do the other. One of my colleagues has just recently implemented a sessionStorage syncing for his app using swr.

Was this page helpful?
0 / 5 - 0 ratings