Replies: 1 comment
-
|
The cross-request contamination concern is valid in principle, but worth examining whether it actually applies to your specific selector before reaching for the QueryClient-mirroring pattern. Why your selector is likely safe as-isYour selectPost is a pure, request-agnostic transform — it only depends on the shape of ApiResponse, not on any user identity or request-specific context. Two requests that receive the same ApiResponse should produce the same result. That's not contamination, that's just memoization working correctly. The issue with your proposed fixOn the server branch, getSelectPost() returns a brand-new memoize instance on every render call. A fresh instance has an empty cache, so it can never hit — you've eliminated contamination but also eliminated memoization entirely on the server. It works, but it's doing nothing useful there. The more idiomatic approach: useMemo inside the hookexport const useGetPostById = <TData = Post>(
id: number,
queryConfig?: ...,
optionalAxiosConfig?: ...,
) => {
const select = useMemo(
() => memoize((response: ApiResponse<Post>) => response.data),
[]
);
return useSuspenseQuery<ApiResponse<Post>, AppError, TData>({
queryKey: postQueryKeys.byId(id),
queryFn: ({ signal }) =>
postsApi.getById(id, { signal, ...optionalAxiosConfig }),
select: select as (response: ApiResponse<Post>) => TData,
...queryConfig,
});
};This sidesteps the SSR concern entirely without any isServer() branching: Server: each request instantiates the component fresh → useMemo creates a new memoize instance → no cross-request state, no memory leak The useMemo dependency array [] is intentional and correct here — the selector's identity doesn't depend on any prop or state, so it should be stable for the full lifetime of the component instance, which is exactly the guarantee you want on the client. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Memoized
selectselector outside a functional component — Next.js SSR concernInspired by @TkDodo this post by TkDodo which mentions memoizing selectors, I wanted to implement the pattern of declaring the
selectfunction outside of a functional component.🧩 Scenario
Looking to implement the pattern mentioned in the post — the one with the
selectfunction declared outside of a functional component — but implementing this in a Next.js application, where even client components are server-side rendered on the first render, would be a little problematic since subsequent requests would return the same cache entry.📄 Current implementation
The hook:
The client component consuming the hook:
❓ Problem
In a Next.js application, the module-level
memoizeinstance is long-lived and shared across all incoming requests on the server. This means that cache entries from different users or requests could contaminate each other — which is the same cross-request state problem that TanStack Query solves forQueryClientby creating a new instance per request on the server.💡 Potential fix
Mirroring the
QueryClientpattern — creating a fresh instance on the server while reusing a singleton on the browser:Is this the right approach, or is there a more idiomatic way to handle memoized selectors in a Next.js SSR context?
Beta Was this translation helpful? Give feedback.
All reactions