Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

useSuspenseQuery do not respect cached data in persister #8400

Closed
lislon opened this issue Dec 4, 2024 · 4 comments
Closed

useSuspenseQuery do not respect cached data in persister #8400

lislon opened this issue Dec 4, 2024 · 4 comments

Comments

@lislon
Copy link

lislon commented Dec 4, 2024

Describe the bug

I would like to pre-cache useQuery data, so when the user visits app a second time, the app will show stale data, and refetch in background.

I used persisted with useQuery before it was working as expected, but when I switched to useSuspenseQuery, the cached data is not used anymore.

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      gcTime: 1000 * 60 * 60 * 24 * 7, // 7 days
      staleTime: 0,
    },
  },
});
// ...
function App() {
  useSuspenseQuery({
    queryKey: ['test'],
    queryFn: async () => {
      await sleep(1000);
      return 'ok';
    },
  });
  return <div>App is ready. Data cached. Try to reload</div>;
}

Your minimal, reproducible example

https://stackblitz.com/edit/vitejs-vite-ihky3z?file=index.tsx

Steps to reproduce

  1. Wait till app is loaded
  2. Observe: The data is cached in localStorage
  3. Press refresh button to reload the page

Expected behavior

The loader should not be shown for second time.

How often does this bug happen?

None

Screenshots or Videos

No response

Platform

  • Chrome 131.0.6778.87 (Official Build) (64-bit)

Tanstack Query adapter

react-query

TanStack Query version

5.62.2

TypeScript version

5.7.2

Additional context

No response

@TkDodo
Copy link
Collaborator

TkDodo commented Jan 11, 2025

Thanks for filing, I think I know why that is. The app continues to render in an unsubscribed state while re-storing from a persisted cache, mainly for SSR reasons.

In that state, we’re not supposed to fire any fetches, because we aren’t subscribed to the store yet. However, with suspense, we’re throwing promises from the render function directly, so this gets bypassed.

We could stop that fetch from happening, too, however, then useSuspenseQuery would return undefined as data on that render cycle, which is something that useSuspenseQuery isn’t supposed to do. data should always be defined with it.

So I think suspense is just not really compatible with restoring data like that. The best workaround I can offer is writing a PersistGate that stops the app from rendering altogether while you’re restoring.

Here’s a fork that shows this, and it works like you’d expect:

https://stackblitz.com/edit/vitejs-vite-zxnzxn3p?file=index.tsx

As App isn’t rendered while the restore takes place, suspense won’t have the chance to fire anything. Then, when data is in the cache, you’ll get cached data served.

@TkDodo TkDodo closed this as not planned Won't fix, can't repro, duplicate, stale Jan 11, 2025
@githorse
Copy link

githorse commented Jan 17, 2025

This feels like a major footgun to me. I just switched on the persister and spent a while figuring out why half of my hooks don't seem to use it before realizing that it's the useSuspenseQuery ones. Since this behavior is unexpected, could we add some language about this to the persistQueryClient docs (and maybe the useSuspenseQuery docs)?

But more importantly: we definitely want both suspenses and local storage persistence. In one case of interest, we're fetching app/user config from an API. Since this config has exist before we can call any other API, it's a perfect candidate for a suspense high up in the tree. But, for exactly the same reasons, and because it rarely changes, it's also a perfect candidate for persistence in local storage, to save that round-trip on every refresh. (Especially during development.) How to choose?!? 🤷

Looking at your example above, I wonder if the PersistGate idea is something that could be baked into the PersistQueryClientProvider?

@TkDodo
Copy link
Collaborator

TkDodo commented Jan 19, 2025

I wonder if the PersistGate idea is something that could be baked into the PersistQueryClientProvider?

generally I’d say no, otherwise, this would’ve been the default behaviour. If you have a PersistGate, you can’t have SSR. That’s your decision to make, so you can opt into the PersistGate to get suspense + persist working.

@TkDodo
Copy link
Collaborator

TkDodo commented Jan 19, 2025

I agree it should definitely be updated in the docs. Would be great if you could file a PR 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants