Describe the bug
Hello guys, I am experiencing a weird behavior and investigating a little bit thats what I found:
Several hooks resolve the QueryClient with:
const client = createMemo(() => useQueryClient(queryClient?.()))
useQueryClient (in QueryClientProvider.tsx) calls useContext(QueryClientContext) when no explicit client is passed.
In Solid, useContext is only valid when run in the synchronous, top-level execution of a component / custom hook — not inside a nested reactive scope such as a createMemo callback (which can run later or in a different tracking context).
That mismatch can surface as:
TypeError: Cannot read properties of undefined (reading 'defaultQueryOptions') (or similar) when client() is effectively wrong/undefined during certain navigation / lazy-route / load phases.
Fragile behavior under code-splitting (e.g. lazy-loaded route chunks) where context resolution timing differs.
We hit this in production on SolidStart + file-based routing + lazy routes; we worked around it with a local patch that resolves the context once at hook top level and only uses createMemo for a plain () => QueryClient accessor.
Your minimal, reproducible example
tricky to reproduce, resolving the client via useQueryClient() inside createMemo is not valid for Solid’s context rules
Steps to reproduce
Library code (always reproducible as a pattern violation)
Current main still has the anti-pattern in useBaseQuery:
File: packages/solid-query/src/useBaseQuery.ts
Line ~110: const client = createMemo(() => useQueryClient(queryClient?.()))
The same createMemo(() => useQueryClient(...)) pattern appears in (at least):
useQueries.ts
useMutation.ts
useIsFetching.ts
useIsMutating.ts
useMutationState.ts
**2. App-level runtime repro **
We don’t have a public minimal StackBlitz handy, but the conditions that triggered the bug for us were:
SolidStart (or Vite + Solid Router) with lazy route components (async import()).
App wrapped in QueryClientProvider at the root.
Inside a lazy-loaded route module, call useQuery (or useQueries / useMutation) without passing the optional queryClient accessor, so resolution goes through context.
Navigate to that route (client-side navigation) and exercise the query (mount + suspense / load as applicable).
Observed (intermittent): crash when internal code does client().defaultQueryOptions(...) because client resolved to undefined.
Expected behavior
useContext(QueryClientContext) should run once per hook invocation, at top level of useBaseQuery / useQueries / etc., not inside createMemo.
createMemo should only wrap non-hook work, e.g. reading an optional queryClient accessor and/or calling the context-derived getter.
Conceptually (pseudo-code):
// Top level of the hook (runs synchronously with the component)
const resolveClient = useQueryClientResolver(queryClient) // useContext happens here
const client = createMemo(() => resolveClient())
How often does this bug happen?
Every time
Screenshots or Videos
Platform
Platform
@tanstack/solid-query: 5.96.2 (also checked main on GitHub and the pattern is still present)
solid-js: 1.x
Framework: SolidStart / Vinxi
Additional context
Solid’s reactivity model differs from React: useContext is not safe inside arbitrary memos/computations the same way as “run once per render” in React.
Reference (Solid): Rules of hooks / reactive context usage should be read in the proper scope, not hidden inside deferred reactive callbacks.
Tanstack Query adapter
None
TanStack Query version
5.96.2
TypeScript version
5.7.3
Additional context
Short “why reproduce is tricky” note for you
The bug is probably from source (useContext inside createMemo).
The runtime failure can be timing-dependent (lazy chunks, navigation, SSR/hydration), so a one-file repro may take a bit of iteration.
Proposed fix
Introduce a small resolver (e.g. useQueryClientResolver(optionalAccessor)) that:
Runs useContext(QueryClientContext) once at hook top level (or uses the documented pattern for optional injected client).
Returns a stable function () => QueryClient safe to call from memos / observers.
Replace patterns like
createMemo(() => useQueryClient(queryClient?.()))
with
const resolve = useQueryClientResolver(queryClient) and createMemo(() => resolve()) only if the memo body is allowed to call a non-hook resolver (the resolver itself must not call useContext).
Apply the same pattern everywhere the package currently does createMemo(() => useQueryClient(...)) (useQuery, useQueries, useMutation, useIsFetching, useIsMutating, useMutationState, etc.) so behavior is consistent.
Tests:
Unit or integration test that simulates missing or late context vs injected client and asserts no throw when the resolver is used correctly.
If feasible, a test that forbids useContext from being invoked inside a createMemo callback for these hooks (lint or a small runtime dev check is optional; tests are enough for many reviewers).
I maintain a pnpm patch that introduces useQueryClientResolver and replaces createMemo(() => useQueryClient(...)) across the files above; happy to contribute a PR if this direction matches your preferences
Best regards! 🇧🇷 🚀
Describe the bug
Hello guys, I am experiencing a weird behavior and investigating a little bit thats what I found:
Several hooks resolve the QueryClient with:
const client = createMemo(() => useQueryClient(queryClient?.()))useQueryClient(in QueryClientProvider.tsx) calls useContext(QueryClientContext) when no explicit client is passed.In Solid, useContext is only valid when run in the synchronous, top-level execution of a component / custom hook — not inside a nested reactive scope such as a createMemo callback (which can run later or in a different tracking context).
That mismatch can surface as:
TypeError: Cannot read properties of undefined (reading 'defaultQueryOptions') (or similar) when client() is effectively wrong/undefined during certain navigation / lazy-route / load phases.
Fragile behavior under code-splitting (e.g. lazy-loaded route chunks) where context resolution timing differs.
We hit this in production on SolidStart + file-based routing + lazy routes; we worked around it with a local patch that resolves the context once at hook top level and only uses createMemo for a plain () => QueryClient accessor.
Your minimal, reproducible example
tricky to reproduce, resolving the client via useQueryClient() inside createMemo is not valid for Solid’s context rules
Steps to reproduce
Library code (always reproducible as a pattern violation)
Current main still has the anti-pattern in useBaseQuery:
File: packages/solid-query/src/useBaseQuery.ts
Line ~110: const client = createMemo(() => useQueryClient(queryClient?.()))
The same createMemo(() => useQueryClient(...)) pattern appears in (at least):
**2. App-level runtime repro **
We don’t have a public minimal StackBlitz handy, but the conditions that triggered the bug for us were:
SolidStart (or Vite + Solid Router) with lazy route components (async import()).
App wrapped in QueryClientProvider at the root.
Inside a lazy-loaded route module, call useQuery (or useQueries / useMutation) without passing the optional queryClient accessor, so resolution goes through context.
Navigate to that route (client-side navigation) and exercise the query (mount + suspense / load as applicable).
Observed (intermittent): crash when internal code does client().defaultQueryOptions(...) because client resolved to undefined.
Expected behavior
useContext(QueryClientContext) should run once per hook invocation, at top level of useBaseQuery / useQueries / etc., not inside createMemo.
createMemo should only wrap non-hook work, e.g. reading an optional queryClient accessor and/or calling the context-derived getter.
Conceptually (pseudo-code):
// Top level of the hook (runs synchronously with the component)
How often does this bug happen?
Every time
Screenshots or Videos
Platform
Platform
@tanstack/solid-query: 5.96.2 (also checked main on GitHub and the pattern is still present)
solid-js: 1.x
Framework: SolidStart / Vinxi
Additional context
Solid’s reactivity model differs from React: useContext is not safe inside arbitrary memos/computations the same way as “run once per render” in React.
Reference (Solid): Rules of hooks / reactive context usage should be read in the proper scope, not hidden inside deferred reactive callbacks.
Tanstack Query adapter
None
TanStack Query version
5.96.2
TypeScript version
5.7.3
Additional context
Short “why reproduce is tricky” note for you
The bug is probably from source (useContext inside createMemo).
The runtime failure can be timing-dependent (lazy chunks, navigation, SSR/hydration), so a one-file repro may take a bit of iteration.
Proposed fix
Introduce a small resolver (e.g. useQueryClientResolver(optionalAccessor)) that:
Runs useContext(QueryClientContext) once at hook top level (or uses the documented pattern for optional injected client).
Returns a stable function () => QueryClient safe to call from memos / observers.
Replace patterns like
createMemo(() => useQueryClient(queryClient?.()))with
const resolve = useQueryClientResolver(queryClient)andcreateMemo(() => resolve())only if the memo body is allowed to call a non-hook resolver (the resolver itself must not call useContext).Apply the same pattern everywhere the package currently does
createMemo(() => useQueryClient(...)) (useQuery, useQueries, useMutation, useIsFetching, useIsMutating, useMutationState, etc.)so behavior is consistent.Tests:
Unit or integration test that simulates missing or late context vs injected client and asserts no throw when the resolver is used correctly.
If feasible, a test that forbids useContext from being invoked inside a createMemo callback for these hooks (lint or a small runtime dev check is optional; tests are enough for many reviewers).
I maintain a pnpm patch that introduces useQueryClientResolver and replaces createMemo(() => useQueryClient(...)) across the files above; happy to contribute a PR if this direction matches your preferences
Best regards! 🇧🇷 🚀