Skip to content

queryFn erroneously called in useSuspenseQuery dependent on serial prefetch #8828

Open
@TrevorBurnham

Description

@TrevorBurnham

Describe the bug

I've been struggling to get the Suspense prefetch-with-streaming pattern working in my app when there are multiple prefetch queries (because one prefetch request depends on the result of the other).

The problem is that the queryFn gets called for the second useSuspenseQuery call during SSR. For example:

// function to generate options for the query to get a given Pokemon
export const getPokemonOptions = (id: number) =>
  queryOptions({
    queryKey: ["pokemon", id],
    queryFn: async () => {
      const response = await fetch(`https://pokeapi.co/api/v2/pokemon/${id}`);
      return response.json();
    },
  });

// code in the RSC to perform two serial fetch operations
void queryClient
  .fetchQuery({
    queryKey: ["favoritePokemonId"],
    queryFn: () =>
      // simulate database call to look up the user's favorite Pokemon
      new Promise<number>((resolve) =>
        setTimeout(() => resolve(Math.ceil(Math.random() * 100)), 500)
      ),
  })
  .then((favoritePokemonId) => {
    return queryClient.prefetchQuery(getPokemonOptions(favoritePokemonId));
  });

// code in the Suspense-wrapped client component to hydrate the data
const { data: favoritePokemonId } = useSuspenseQuery({
  queryKey: ["favoritePokemonId"],
  queryFn: (): number => {
    throw new Error("First queryFn in pokemon-info should never be called.");
  },
});
const { data: pokemon } = useSuspenseQuery({
  ...getPokemonOptions(favoritePokemonId),
  queryFn: () => {
    throw new Error("Second queryFn in pokemon-info should never be called");
  },
});

This code causes SSR to fail with the error Second queryFn in pokemon-info should never be called.

Interestingly, if you stub out the queryFn with a no-op function, the code seems to work, with the second useSuspenseQuery returning data immediately. That suggests that the Suspense boundary is correctly blocking the component with the useSuspenseQuery from rendering until the prefetch has completed, but then it's incorrectly trying to fetch the prefetched data again, even with staleTime: Infinity.

Your minimal, reproducible example

https://codesandbox.io/p/devbox/dreamy-sky-znq6jh?workspaceId=ws_CZBXqWgnzWZNdzjDui2die

Steps to reproduce

  1. Prefetch data serially without await to allow for streaming
  2. Use useSuspenseQuery for all prefetched data from a component inside of a <Suspense> boundary

Expected behavior

The clientFn provided to useSuspenseQuery should never be called during SSR, because the hook is for loading prefetched data. (It should only be called on the client after the data is invalidated.)

How often does this bug happen?

Every time

Platform

macOS, but reproducible in CodeSandbox. Reproducible under Next.js 15 w/ React 19, and Next.js 14 w/ React 18.3.

TanStack Query version

5.68.0

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions