T
TanStack2y ago
rising-crimson

Fetch during server side render

I am trying to run my useQuery's during SSR on a custom Vite setup, but for some reason it is not working due to do queries being in the idle fetchingStatus while their status is loading. When does the fetchingStatus change? And how can I force this/wait for this during SSR? Thanks
5 Replies
rising-crimson
rising-crimsonOP2y ago
Part of my SSR code for reference:
while (true) {
renderToString(<App {...appProps} />);

const isSsr = (query: Query) => !!query.meta?.ssr;

console.log(
queryClient
.getQueryCache()
.getAll()
.map((q) => [q.state.status, q.state.fetchStatus, q.meta?.ssr]),
);
if (!queryClient.isFetching({ predicate: isSsr })) {
console.log('done fetching');
break;
}

console.log('still fetching');

await new Promise((resolve) => {
const unsubscribe = queryClient.getQueryCache().subscribe(() => {
if (!queryClient.isFetching({ predicate: isSsr })) {
unsubscribe();
resolve(undefined);
}
});
});
}
while (true) {
renderToString(<App {...appProps} />);

const isSsr = (query: Query) => !!query.meta?.ssr;

console.log(
queryClient
.getQueryCache()
.getAll()
.map((q) => [q.state.status, q.state.fetchStatus, q.meta?.ssr]),
);
if (!queryClient.isFetching({ predicate: isSsr })) {
console.log('done fetching');
break;
}

console.log('still fetching');

await new Promise((resolve) => {
const unsubscribe = queryClient.getQueryCache().subscribe(() => {
if (!queryClient.isFetching({ predicate: isSsr })) {
unsubscribe();
resolve(undefined);
}
});
});
}
The query I am trying to ssr has the log ['loading','idle', true] I have tried using networkMode: 'always' but this did not change anything. My query is as follows:
const { data: data2 } = useQuery({
queryKey: ['authenticatedPing'],
queryFn: async () => {
await sleep(100);
return 'XD';
},
meta: { ssr: true },
});
const { data: data2 } = useQuery({
queryKey: ['authenticatedPing'],
queryFn: async () => {
await sleep(100);
return 'XD';
},
meta: { ssr: true },
});
flat-fuchsia
flat-fuchsia2y ago
useQuery doesn't render on the server because the initial fetch is triggered by useSyncExternalStore, and the subscribe function is like a useEffect - it only runs onthe client. that's why our SSR docs state that you need to manually prefetch on the server and use the HydrationBoundary component to hydrate data on the client unless you build suspsense on the server, like the app directory can do. In that case, useSuspenesQuery will run on the server, but you still need to inject into the stream to hydrate data on the client. we have an experimental nextJs integration for that
rising-crimson
rising-crimsonOP2y ago
Aha I see. Is the subscription to some internal event bus that orchestrates queries or something like that? I haven't worked with useSyncExternalStore before so curious about how you would use it here I previously had a Suspense approach implemented but couldn't figure out a way to render a fallback if it took too long, so I decided to give it a shot without suspense. Appreciate the quick response! I will just ditch this approach entirely I guess. I'm using tRPC which can already do all the prefetchQuery calls for me, and using useQuery + fetch on the server without tRPC would require me to spoof the request as if it came from the user, which is not nice either 👍
flat-fuchsia
flat-fuchsia2y ago
think of useSyncExternalStore a bit like useState + useEffect, where the effect fires the request and then calls the setter to trigger a re-render. The effect also won't run on the server, and neither will uSES
rising-crimson
rising-crimsonOP2y ago
Oo that makes sense. Much better than having stateful variables inside a global context because because that would rerender everything

Did you find this page helpful?