You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
solid-query 6.0.0-rc.5: onCacheEvent throws NotReadyError during a Solid 2 (rc.14+) transition landing #12031
With Solid 2 rc.14+ (solid-js / @solidjs/signals 2.0.0-rc.15), useBaseQuery's onCacheEvent can throw NotReadyError while a transition lands. The throw escapes unhandled, aborts the landing, and the next transition never commits.
The chain:
A transition lands, and an unmounting useQuery runs its onCleanup: observer.destroy() → QueryCache.notify.
The notify reaches a sibling useQuery on the same key that was created inside that (still held) transition.
Its onCacheEvent compares event.query.queryHash === untrack(defaultedOptions).queryHash. defaultedOptions is a memo born held, and since rc.14 an untracked read of such a node throws NotReadyError (Solid 2 diagnostic A19, exception 1).
In an app with @solidjs/router this looks like a navigation that never finishes.
Your minimal, reproducible example
Inline below (vitest + jsdom; no app code). jsdom is needed because isServer is typeof window === "undefined", and without it the cache subscription is never created.
import{QueryClient,useQuery}from"@tanstack/solid-query";import{createMemo,createRenderEffect,createRoot,createSignal,flush,onCleanup}from"solid-js";import{expect,it}from"vitest";consttick=()=>newPromise((r)=>setTimeout(r,0));it("query torn down on landing must not abort it for a sibling born in the transition",async()=>{constgates=[];constreleaseAll=async()=>{for(leti=0;i<6;i++){for(constopenofgates.splice(0))open();awaittick();flush();awaittick();}};constflight=(k)=>(k>0 ? newPromise((r)=>gates.push(r)).then(()=>k) : Promise.resolve(k));constclient=newQueryClient({defaultOptions: {queries: {retry: false,gcTime: Infinity}}});const[s,setS]=createSignal(0);constshown=[];constdispose=createRoot((done)=>{constscreen=createMemo(()=>{constk=s();constq=useQuery(()=>({queryKey: ["repro"],queryFn: async()=>"answer"}),()=>client);return()=>(q.isSuccess ? k : -1);});constpending=createMemo(async()=>flight(s()));createRenderEffect(()=>[screen()(),pending()],([k])=>{shown.push(k);},);returndone;});flush();awaitreleaseAll();setS(1);flush();awaittick();setS(2);flush();awaitreleaseAll();setS(3);flush();awaitreleaseAll();expect(s()).toBe(3);expect(shown.at(-1)).toBe(3);dispose();client.clear();});
Run with MODE=prod NODE_ENV=production npx vitest run repro.test.mjs, or MODE=dev NODE_ENV=development for the dev build.
We confirmed the source of the throw by temporarily wrapping untrack(defaultedOptions) in onCacheEvent with a try/catch, which caught NotReadyError on cache events during the landing.
Expected behavior
The test passes: onCacheEvent does not throw during a landing, and the third transition commits (s() === 3, last rendered value 3).
Possible fix
observer.options is what the committed render effect last passed to observer.setOptions, so it holds the hook's current hash in a plain object instead of the reactive graph:
- if (event.query.queryHash === untrack(defaultedOptions).queryHash || event.query.queryHash === latestHash) {+ if (event.query.queryHash === observer.options.queryHash || event.query.queryHash === latestHash) {
With this change the reproduction passes in both builds. We have not run the solid-query test suite against it.
How often does this bug happen?
Every time
Platform
Node v25.8.2, macOS; vitest 4.1.11, jsdom 29.1.1. The navigation hang also shows up in Chrome.
Activity
UzumakiODZ commentedon Oct 15, 2026
I would like to work on this issue
niukanen1 commentedon Oct 15, 2026
I looked into this and opened #12034 - happy to adjust if you had something else in mind.