Skip to content

Conversation

nlynzaad
Copy link
Contributor

@nlynzaad nlynzaad commented Sep 11, 2025

resolves #5123 and adds checks to tests to ensure this runs as minimally as possible

Summary by CodeRabbit

  • Performance
    • Reuses existing route matches to reduce redundant param parsing and stabilize navigation/loading behavior.
  • Tests
    • Updated tests to match the new postId param shape, navigation paths, and parameter displays; added mocks to verify param parsing calls.

Copy link
Contributor

coderabbitai bot commented Sep 11, 2025

Walkthrough

Reworks router-core match construction to reuse existing match _strictParams and only run params.parse when necessary, attaching any parse error to the match. Updates React and Solid tests to use string postId params, adjust loaders to parseInt(params.postId), change links to to="./$postId" with params, and add vi-based mocking for parse instrumentation.

Changes

Cohort / File(s) Summary
Core router param flow and match reuse
packages/router-core/src/router.ts
Compute matchId earlier, lookup existingMatch and previousMatch; reuse existingMatch._strictParams when available; run params.parse only when there is no existing match; attach paramsError to match instead of earlier parseErrors aggregation; merge strictParams into routeParams.
React useParams tests refactor
packages/react-router/tests/useParams.test.tsx
Import vi, add vi.fn() mock; replace numeric id param with string postId; update params.parse/stringify, loader uses parseInt(params.postId); links changed to to="./$postId" with params; UI/test assertions updated to check param_postId_value; removed Id_Param display.
Solid useParams tests refactor
packages/solid-router/tests/useParams.test.tsx
Same test changes as React: import vi, use vi.fn() mock, switch to postId string param, update parse/stringify, loader uses parseInt(params.postId), links use to="./$postId" with params, and tests assert on param_postId_value and navigation paths.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Router
  participant MatchStore as Match Store
  participant Parser as Params Parser
  participant Loader

  User->>Router: navigate(path, opts)
  Router->>Router: interpolatePath(path) -> usedParams
  Router->>Router: compute loaderDepsHash
  Router->>MatchStore: get(matchId = interpolatedPath + depsHash)
  alt existing match found
    MatchStore-->>Router: existingMatch
    Router->>Router: strictParams = existingMatch._strictParams
  else no existing match
    Router->>Parser: run route.params.parse(strictParams) if configured
    alt parse success
      Parser-->>Router: strictParams
    else parse failure
      Parser-->>Router: throw or return paramsError
      Router->>Router: attach paramsError to match (or throw if opts.throwOnError)
    end
  end
  Router->>Router: routeParams = {...routeParams, ...strictParams}
  Router->>Loader: invoke loader(routeParams) as needed
  Loader-->>Router: data
  Router->>MatchStore: store match {_strictParams, paramsError, data}
  Router-->>User: update state / render
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • schiller-manuel
  • SeanCassiere

Pre-merge checks (4 passed, 1 warning)

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "fix(router-core): params.parse should run as minimally as possible" is concise, specific, and accurately reflects the core change in packages/router-core/src/router.ts (reducing when per-route params.parse is invoked and reusing existing matches), so it communicates the primary intent to reviewers.
Linked Issues Check ✅ Passed The changes address linked issue #5123 by removing the global per-route parsing pass and deferring per-route params.parse so it runs only for new matches, which ensures parse is applied to interpolatePathResult.usedParams (the raw string values); the updated tests in packages/react-router and packages/solid-router assert string param behavior and use mocks to verify minimal parse invocations, demonstrating the intended fix.
Out of Scope Changes Check ✅ Passed I do not see out-of-scope modifications: router-core changes are focused on parsing behavior and match reuse, and the changes in framework test files (param name adjustment, vi mocks, link updates) are test fixture updates aligned with the new param shape and parse semantics rather than unrelated feature work.

Poem

In tunnels of routes I hop with delight,
I keep postId strings snug, parse numbers at night.
Old matches I reuse, less hustle, more grace,
vi counts the parses — a neat little trace.
Hooray for $postId paths, bound post to post with pace. 🐰✨

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.

✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch #5123

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

nx-cloud bot commented Sep 11, 2025

View your CI Pipeline Execution ↗ for commit 4858951

Command Status Duration Result
nx affected --targets=test:eslint,test:unit,tes... ✅ Succeeded 1m 57s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 3s View ↗

☁️ Nx Cloud last updated this comment at 2025-09-11 13:24:23 UTC

Copy link

pkg-pr-new bot commented Sep 11, 2025

More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/arktype-adapter@5124

@tanstack/directive-functions-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/directive-functions-plugin@5124

@tanstack/eslint-plugin-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/eslint-plugin-router@5124

@tanstack/history

npm i https://pkg.pr.new/TanStack/router/@tanstack/history@5124

@tanstack/react-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router@5124

@tanstack/react-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router-devtools@5124

@tanstack/react-router-ssr-query

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router-ssr-query@5124

@tanstack/react-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start@5124

@tanstack/react-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-client@5124

@tanstack/react-start-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-plugin@5124

@tanstack/react-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-server@5124

@tanstack/router-cli

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-cli@5124

@tanstack/router-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-core@5124

@tanstack/router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-devtools@5124

@tanstack/router-devtools-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-devtools-core@5124

@tanstack/router-generator

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-generator@5124

@tanstack/router-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-plugin@5124

@tanstack/router-ssr-query-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-ssr-query-core@5124

@tanstack/router-utils

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-utils@5124

@tanstack/router-vite-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-vite-plugin@5124

@tanstack/server-functions-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/server-functions-plugin@5124

@tanstack/solid-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router@5124

@tanstack/solid-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router-devtools@5124

@tanstack/solid-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start@5124

@tanstack/solid-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-client@5124

@tanstack/solid-start-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-plugin@5124

@tanstack/solid-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-server@5124

@tanstack/start-client-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-client-core@5124

@tanstack/start-plugin-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-plugin-core@5124

@tanstack/start-server-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-server-core@5124

@tanstack/start-server-functions-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-server-functions-client@5124

@tanstack/start-server-functions-fetcher

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-server-functions-fetcher@5124

@tanstack/start-server-functions-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-server-functions-server@5124

@tanstack/start-storage-context

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-storage-context@5124

@tanstack/valibot-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/valibot-adapter@5124

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/TanStack/router/@tanstack/virtual-file-routes@5124

@tanstack/zod-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/zod-adapter@5124

commit: 4858951

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9a86563 and 9ad7369.

📒 Files selected for processing (3)
  • packages/react-router/tests/useParams.test.tsx (5 hunks)
  • packages/router-core/src/router.ts (2 hunks)
  • packages/solid-router/tests/useParams.test.tsx (5 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test
  • GitHub Check: Preview
🔇 Additional comments (5)
packages/router-core/src/router.ts (3)

1200-1209: LGTM! Optimization correctly reuses existing match data

The implementation correctly prevents redundant parsing by checking for an existing match and reusing its _strictParams. This directly addresses the issue where params.parse was being called unnecessarily.


1214-1239: Robust implementation of conditional parameter parsing

The logic correctly:

  1. Reuses _strictParams from existing matches when available
  2. Falls back to interpolatePathResult.usedParams for new matches
  3. Only invokes params.parse when there's no existing match and parsing is configured
  4. Properly handles errors with the new paramsError variable

This ensures params.parse runs minimally as intended.


1241-1241: Good use of Object.assign for parameter merging

Using Object.assign(routeParams, strictParams) efficiently merges the strict parameters into the route parameters object.

packages/react-router/tests/useParams.test.tsx (2)

73-85: Test correctly validates params.parse minimization

The test setup appropriately:

  • Adds console.log() to track parse invocations
  • Converts the parse function to work with string params (postId as '1' or '2')
  • Updates the loader to use parseInt(params.postId) for type conversion

This validates that the parse function receives the correct string type as expected per issue #5123.


186-250: Comprehensive test assertions for parse invocation tracking

The test thoroughly validates the optimization by:

  • Clearing and checking console spy at each navigation step
  • Asserting specific call counts to ensure parse runs minimally
  • Verifying the correct param values are displayed

Excellent test coverage for the PR objectives.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
packages/solid-router/tests/useParams.test.tsx (1)

27-27: Resolved: replaced no-op console.log with a vi.fn spy.

Good switch to a proper spy for deterministic assertions.

packages/react-router/tests/useParams.test.tsx (1)

27-27: Resolved: replaced no-op console.log with a vi.fn spy.

Nice cleanup; mirrors the Solid test approach.

🧹 Nitpick comments (9)
packages/solid-router/tests/useParams.test.tsx (4)

75-82: Avoid defaulting to '2' in parse; use an explicit map or pass-through.

Defaulting any non-'one' value to '2' can mask issues and inflate parse counts. Map known values and otherwise pass through.

-      parse: (params) => {
-        mockedfn()
-        return {
-          ...params,
-          postId: params.postId === 'one' ? '1' : '2',
-        }
-      },
+      parse: (params) => {
+        mockedfn()
+        const idMap = { one: '1', two: '2' } as const
+        return { ...params, postId: idMap[params.postId] ?? params.postId }
+      },

85-85: Specify radix for parseInt.

Be explicit to avoid edge cases with leading zeros.

-      post: posts.find((post) => post.id === parseInt(params.postId)),
+      post: posts.find((post) => post.id === parseInt(params.postId, 10)),

191-191: Parse call-count assertions are brittle.

Exact-call assertions may fail with benign internal changes. Prefer asserting upper bounds and/or validating parse inputs.

Example adjustments:

  • Upper bound: expect(mockedfn.mock.calls.length).toBeLessThanOrEqual(N)
  • Validate inputs (Solid): capture params passed into parse with a vi.fn wrapper and assert postId is 'one'/'two' on respective navigations.

Also applies to: 214-214, 225-225, 247-247


184-184: Reduce repetition for mock resets.

Extract a helper or reset in a scoped utility to keep the test flow concise.

-  mockedfn.mockClear()
+  // helper
+  const resetParseSpy = () => mockedfn.mockClear()
+  resetParseSpy()

Also applies to: 193-193, 217-217, 227-227

packages/react-router/tests/useParams.test.tsx (5)

74-77: Specify radix for parseInt in loader.

-    loader: ({ params }) => {
-      return { post: posts.find((post) => post.id === parseInt(params.postId)) }
-    },
+    loader: ({ params }) => {
+      return { post: posts.find((post) => post.id === parseInt(params.postId, 10)) }
+    },

78-85: Avoid defaulting to '2' in parse; use an explicit map or pass-through.

-    params: {
-      parse: (params) => {
-        mockedfn()
-        return {
-          ...params,
-          postId: params.postId === 'one' ? '1' : '2',
-        }
-      },
-    },
+    params: {
+      parse: (params) => {
+        mockedfn()
+        const idMap = { one: '1', two: '2' } as const
+        return { ...params, postId: idMap[params.postId] ?? params.postId }
+      },
+    },

177-177: Use asynchronous act correctly with promises.

Wrap the awaited call in an async act block to avoid warnings.

-  await act(() => router.load())
+  await act(async () => {
+    await router.load()
+  })

186-186: act around sync clicks doesn’t need await; make it consistently sync.

Keeps intent clear and avoids implying asynchrony where there is none.

-  await act(() => fireEvent.click(firstCategoryLink))
+  act(() => {
+    fireEvent.click(firstCategoryLink)
+  })

Apply similarly to the other click handlers in this file.

Also applies to: 195-195, 220-220, 230-230


192-192: Parse call-count assertions may be too strict.

Assert upper bounds and/or validate the params received by parse to reduce brittleness while still guarding “minimal” execution.

Example:

expect(mockedfn.mock.calls.length).toBeLessThanOrEqual(N)
// or
// const parsePost = vi.fn((p) => ({...}))
// expect(parsePost).toHaveBeenLastCalledWith(expect.objectContaining({ postId: 'one' }))

Also applies to: 216-216, 227-227, 249-249

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9ad7369 and 4858951.

📒 Files selected for processing (2)
  • packages/react-router/tests/useParams.test.tsx (5 hunks)
  • packages/solid-router/tests/useParams.test.tsx (5 hunks)
🔇 Additional comments (2)
packages/solid-router/tests/useParams.test.tsx (1)

123-125: LGTM: relative to="./$postId" with params.

This properly defers encoding to the router and avoids manual string interpolation.

packages/react-router/tests/useParams.test.tsx (1)

124-126: LGTM: to="./$postId" with params.

Removes brittle string interpolation and leverages route semantics.

@nlynzaad nlynzaad merged commit b6a9880 into main Sep 11, 2025
6 checks passed
@nlynzaad nlynzaad deleted the #5123 branch September 11, 2025 15:07
nlynzaad added a commit that referenced this pull request Sep 11, 2025
LadyBluenotes pushed a commit to LadyBluenotes/router that referenced this pull request Sep 19, 2025
…anStack#5124)

resolves TanStack#5123 and adds checks to tests to ensure this runs as minimally
as possible

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Performance**
* Reuses existing route matches to reduce redundant param parsing and
stabilize navigation/loading behavior.
* **Tests**
* Updated tests to match the new postId param shape, navigation paths,
and parameter displays; added mocks to verify param parsing calls.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BREAKING] 1.131.37 route params are not of promised type

1 participant