fix(solid-router): keep route-scoped accessors readable after navigation teardown#7607
Conversation
…ion teardown Route-scoped accessors like Route.useParams() are backed by a lazy memo in useMatch. When async work created by a route component read the accessor after navigating away, the memo re-executed against the new matches and threw "Invariant failed: Could not find an active match". Track disposal of the owning reactive scope via onCleanup and return the last known value for reads that happen after teardown, while still throwing for mounted components that read a genuinely missing match. Fixes TanStack#7331 Closes TanStack#7330 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
View your CI Pipeline Execution ↗ for commit db4b860
☁️ Nx Cloud last updated this comment at |
Merging this PR will not alter performance
Warning Please fix the performance issues or acknowledge them on CodSpeed. Performance Changes
Tip Investigate this regression by commenting Comparing Footnotes
|
Fixes #7331
Closes #7330 (includes its failing test)
Problem
In Solid Router v2, route-scoped accessors such as
Route.useParams()andRoute.useSearch()are backed by a lazycreateMemoinuseMatch. If async work created by a route component (e.g. inside acreateEffector a pending promise) reads the accessor after navigating away, the memo re-executes against the current store state. The match is no longer inrouter.stores.matchesand the navigation has finished (no pending match, not transitioning), so the read threw:Fix
useMatchnow registers anonCleanupon the owning reactive scope. Once that scope is disposed (the route was unmounted), reads of the accessor return the last known value instead of throwing.This is intentionally scoped to teardown: a still-mounted component that reads a genuinely missing match still throws, preserving the documented
shouldThrowsemantics and existing tests. SinceuseParams,useSearch,useLoaderData, anduseRouteContextall route throughuseMatch, the fix covers all route-scoped accessors.Testing
@tanstack/solid-routersuite: 46 files, 814 passed, 2 skipped, no type errors🤖 Generated with Claude Code