Skip to content

Solid Router route-scoped accessors can throw during navigation teardown #7331

Description

@s3bba

Which project does this relate to?

Router

Describe the bug

In Solid 2.0 and TanStack Solid Router, route scoped accessors such as Route.useParams() and Route.useSearch() can throw after navigation has moved away from the route that created the accessor.

The test creates a route component that calls postRoute.useParams(), schedules a delayed read of that accessor, navigates away, and then lets the delayed read run. On current code, the delayed read throws:

Invariant failed: Could not find an active match from "/posts/$postId"

Obviosly you would never write such code for the actual applications, but the async reading of accessors can be triggered in createEffect() calls.

Reduced test code snippet:

component: functionPostComponent(){constparams=postRoute.useParams()// Simulated async work that reads params accessordelayedRead=delayedReadGate.then(()=>{try{params()}catch(err){delayedError=err}})return<h1>Post {params().postId}</h1>}
awaitrouter.navigate({to: '/other'})// Trigger async workreleaseDelayedRead()awaitdelayedReadif(delayedError){thrownewError(`Route-scoped useParams threw after navigation away: ${delayedErrorinstanceofError ? delayedError.message : String(delayedError)}`,)}

The current behavior demonstrates that a route-created accessor can become throwing solely because active matches no longer contain that route id.

Complete minimal reproducer

#7330

Steps to Reproduce the Bug

Run the added test in the pull request

CI=1 NX_DAEMON=false pnpm nx run @tanstack/solid-router:test:unit --outputStyle=stream --skipRemoteCache -- tests/useMatch.test.tsx
@tanstack/solid-router: ⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯
@tanstack/solid-router: FAIL @tanstack/solid-router tests/useMatch.test.tsx > useMatch > route-scoped useParams should remain readable from async work created by the route during navigation away
@tanstack/solid-router: Error: Route-scoped useParams threw after navigation away: Invariant failed: Could not find an active match from "/posts/$postId"
@tanstack/solid-router: ❯ tests/useMatch.test.tsx:170:13
@tanstack/solid-router: 168|
@tanstack/solid-router: 169| if (delayedError) {
@tanstack/solid-router: 170| throw new Error(
@tanstack/solid-router: | ^
@tanstack/solid-router: 171| `Route-scoped useParams threw after navigation away: ${
@tanstack/solid-router: 172| delayedError instanceof Error
@tanstack/solid-router: ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯
@tanstack/solid-router: Test Files 1 failed | 44 passed (45)
@tanstack/solid-router: Tests 1 failed | 802 passed | 2 skipped (805)
@tanstack/solid-router: Type Errors no errors
@tanstack/solid-router: Start at 09:26:49
@tanstack/solid-router: Duration 52.71s (transform 95.49s, setup 12.36s, import 110.95s, tests 93.44s, environment 30.98s, typecheck 3.90s)
@tanstack/solid-router: ELIFECYCLE Command failed with exit code 1.

Expected behavior

Expected behaviour here is up to debate, since route is no longer active and JavaScript does not support proper abort handling.

I have created a workaround for my application that provides params as T | undefined, this removes the errors but discards all the hard work Tanstack router has done on type safe path and search params. This is not the fix I would like to use long term.

typeRegisteredRouteId=RouteIds<RegisteredRouter["routeTree"]>;exporttypeRouteParams<TFromextendsRegisteredRouteId>=Expand<RouteById<RegisteredRouter["routeTree"],TFrom>["types"]["allParams"]>;exportconstuseStableParams=<constTFromextendsRegisteredRouteId>(from: TFrom,): Accessor<RouteParams<TFrom>|undefined>=>useParams<RegisteredRouter,TFrom,true,false,unknown>({
from,shouldThrow: false,});

From my perspective there are two sane options, Route.useParams() could

  1. return the last value instead of throwing on route changes
  2. support the extra api (e.g.: Route.useParams({ shouldThrow: false })) and let the developer of the application handle

Screenshots or Videos

No response

Platform

  • Router / Start Version: Router: 2.0.0-beta.17 for the router and 2.0.0-beta.18 for the start
  • OS: N/A
  • Browser: N/A
  • Browser Version: N/A
  • Bundler: N/A
  • Bundler Version: N/A

Additional context

No response

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions