diff --git a/src/pages/tldraw/singlePlayerPage.tsx b/src/pages/tldraw/singlePlayerPage.tsx index 304eb0c..e9f22f5 100644 --- a/src/pages/tldraw/singlePlayerPage.tsx +++ b/src/pages/tldraw/singlePlayerPage.tsx @@ -65,6 +65,7 @@ export default function SinglePlayerPage() { error: '' }); const storeRef = useRef(null); + const prevStoreRef = useRef(null); // holds old store until tldraw has unmounted cleanly const [storeReady, setStoreReady] = useState(false); // TLDraw user preferences @@ -139,6 +140,10 @@ export default function SinglePlayerPage() { }, 2000); }); + // Dispose the previous store only AFTER we have a new one ready. + // Disposing while storeReady=true would crash tldraw's reactive signals. + prevStoreRef.current?.dispose(); + prevStoreRef.current = null; storeRef.current = newStore; setStoreReady(true); setCanvasPhase('ready'); @@ -154,8 +159,13 @@ export default function SinglePlayerPage() { snapshotServiceRef.current?.forceSaveCurrentNode().catch(() => {}); snapshotServiceRef.current?.clearCurrentNode(); snapshotServiceRef.current = null; - storeRef.current?.dispose(); - storeRef.current = null; + // Don't dispose the store here — Tldraw is still subscribed via storeReady=true. + // Move the current store to prevStoreRef; it will be disposed when the next store is ready. + // This prevents tldraw's reactive signals from reading a disposed store during React's async unmount. + if (storeRef.current) { + prevStoreRef.current = storeRef.current; + storeRef.current = null; + } setStoreReady(false); setCanvasPhase('idle'); };