Skip to content

fix: params becoming undefined in outgoing components during navigation#566

Open
brenelz wants to merge 1 commit into
solidjs:nextfrom
brenelz:fix/stale-params-on-navigation
Open

fix: params becoming undefined in outgoing components during navigation#566
brenelz wants to merge 1 commit into
solidjs:nextfrom
brenelz:fix/stale-params-on-navigation

Conversation

@brenelz

@brenelz brenelz commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes the "2.0 router params are broken" report (params turning undefined in preloads/effects when navigating away, updated params reaching suspended components). Reproduction from the report: https://stackblitz.com/edit/solidjs-templates-aesc6u3m?file=src%2FApp.tsx

Two distinct bugs:

1. Route roots were disposed on every navigation

In Solid 2 beta, createRoot attaches to the current owner and is disposed when that owner's computation re-runs. The per-route roots in Routes were created inside the routeStates memo, so every re-run of that memo disposed all route roots, silently killing any reactive nodes they owned. Roots are now created under the Routes component owner via runWithOwner, restoring the intended "detached root + manual disposers" semantics.

2. Outgoing routes observed the next route's params

useParams(), component params, and preload all shared a single router-level proxy over the current matches. Params flip to the new route at the start of navigation, while Solid 2's deferred disposal lets the outgoing tree's computations still run - so effects re-ran and async fetches refetched with undefined params (especially visible with a <Loading> boundary wrapping the app keeping the outgoing tree alive). The routeMatches[i] ?? routeMatches[0] fallback could also hand a route context another route's match entirely.

Now each route context gets:

  • a sticky matchesAtLevel memo that retains its last valid matches once its route no longer matches, and
  • params scoped to its own lifetime, still merged across all match levels (so layouts reading child params keep working), passed to its component and preload.

useParams() returns the nearest route's params (base route falls back to router-level params), and wrapParams preserves paramsWrapper support for non-Proxy environments.

Tests

  • New regression test reproducing the report: a suspending destination route behind a <Loading> boundary - fails on next (outgoing component refetches with [null, null]), passes with this fix
  • Same-route param navigation still updates params
  • Parent layouts still see child params
  • Preload receives the route's own params during navigation
  • All 230 tests, tsc, type tests, and build pass

Solid 2 disposes roots created inside a computation when it re-runs, so
route roots created in the routeStates memo were disposed on every
navigation, killing reactive nodes they owned. Roots are now created
under the Routes component owner.

Route contexts also get params scoped to their own lifetime via a
sticky matches memo that retains its last valid value while the route
is torn down. Components, effects, and preloads in outgoing routes
(e.g. kept alive by a Loading boundary) no longer observe another
route's params or refetch with undefined values. Also removes the
routeMatches[0] fallback that could hand a context the wrong match.
@changeset-bot

changeset-bot Bot commented Jul 6, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2596327

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@solidjs/router Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant