Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 22 additions & 7 deletions benchmarks/client-nav/react/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ import {
createRootRoute,
createRoute,
createRouter,
useMatch,
useParams,
useSearch,
} from '@tanstack/react-router'

function runPerfSelectorComputation(seed: number) {
let value = Math.trunc(seed) | 0

for (let index = 0; index < 100; index++) {
for (let index = 0; index < 50; index++) {
value = (value * 1664525 + 1013904223 + index) >>> 0
}

Expand All @@ -23,21 +24,27 @@ function runPerfSelectorComputation(seed: number) {
const selectors = Array.from({ length: 20 }, (_, index) => index)

function Params() {
const params = useParams({
const number = useParams({
strict: false,
select: (params) => runPerfSelectorComputation(Number(params.id ?? 0)),
})
void params
return null
return runPerfSelectorComputation(number)
}

function Search() {
const search = useSearch({
const number = useSearch({
strict: false,
select: (search) => runPerfSelectorComputation(Number(search.id ?? 0)),
})
void search
return null
return runPerfSelectorComputation(number)
}

function Match() {
const number = useMatch({
strict: false,
select: (match) => runPerfSelectorComputation(Number(match.params.id ?? 0)),
})
return runPerfSelectorComputation(number)
Comment on lines +42 to +47
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# First, understand the complete structure of the benchmark file
echo "=== BENCHMARK FILE STRUCTURE ==="
wc -l benchmarks/client-nav/react/app.tsx

Repository: TanStack/router

Length of output: 131


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Get the full file to understand route structure and Match usage
echo "=== FULL BENCHMARK FILE ==="
cat -n benchmarks/client-nav/react/app.tsx

Repository: TanStack/router

Length of output: 3066


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find useMatch implementation and its behavior
echo "=== USEWATCH IMPLEMENTATION ==="
find . -type f \( -name "*.ts" -o -name "*.tsx" \) | xargs grep -l "export.*useMatch\|export const useMatch" | head -5

Repository: TanStack/router

Length of output: 289


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search for useMatch in the main source code
rg -nP 'export.*function useMatch|export const useMatch' --type=ts --type=tsx

Repository: TanStack/router

Length of output: 86


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Get useMatch implementation
echo "=== USEWATCH IMPLEMENTATION ==="
cat -n packages/react-router/src/useMatch.tsx

Repository: TanStack/router

Length of output: 4193


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search for how useMatch resolves when from is not specified
rg -nP 'matchContext|nearestMatchId|from.*:' packages/react-router/src/useMatch.tsx -A5 -B2

Repository: TanStack/router

Length of output: 1035


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check what strict: false does in useMatch
rg -nP 'strict.*false|strict.*true|strict\s*:' packages/react-router/src/useMatch.tsx -B3 -A3

Repository: TanStack/router

Length of output: 41


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Look at useSearch and useParams to understand the pattern
echo "=== COMPARISON: useParams IMPLEMENTATION ==="
rg -nP 'export function useParams|export const useParams' -A30 packages/react-router/src/ --type ts --type tsx | head -50

Repository: TanStack/router

Length of output: 148


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check matchContext to understand what nearestMatchId represents
cat -n packages/react-router/src/matchContext.tsx

Repository: TanStack/router

Length of output: 434


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify useParams implementation to compare patterns
cat -n packages/react-router/src/useParams.tsx | head -100

Repository: TanStack/router

Length of output: 3445


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check how Root component would be rendered and what match context it would have
# Also verify the route structure initialization
cat -n benchmarks/client-nav/react/app.tsx | grep -A50 "const root\|const route\|createRouter"

Repository: TanStack/router

Length of output: 2385


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Look for where matchContext is set/provided in the router
rg -nP 'matchContext\.Provider|matchContext.*value' packages/react-router/src/ -B2 -A2

Repository: TanStack/router

Length of output: 1446


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check the Match component to see how matchContext is set
cat -n packages/react-router/src/Match.tsx | head -100

Repository: TanStack/router

Length of output: 4209


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify the root route setup - does it have params?
rg -nP 'createRootRoute|path.*root' benchmarks/client-nav/react/app.tsx -B2 -A5

Repository: TanStack/router

Length of output: 347


Bind useMatch to the /$id route to access its parameters.

The Match() component is rendered inside the root route's match context. When useMatch({ strict: false }) is called without a from option, it resolves to the nearest match—the root route—which has no id parameter. This causes match.params.id to always be undefined, falling back to 0, which doesn't reflect actual navigation state and reduces performance variation in this benchmark.

Suggested fix
 function Match() {
   const number = useMatch({
-    strict: false,
-    select: (match) => runPerfSelectorComputation(Number(match.params.id ?? 0)),
+    from: '/$id',
+    shouldThrow: false,
+    select: (match) => runPerfSelectorComputation(Number(match?.params.id ?? 0)),
   })
   return runPerfSelectorComputation(number)
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function Match() {
const number = useMatch({
strict: false,
select: (match) => runPerfSelectorComputation(Number(match.params.id ?? 0)),
})
return runPerfSelectorComputation(number)
function Match() {
const number = useMatch({
from: '/$id',
shouldThrow: false,
select: (match) => runPerfSelectorComputation(Number(match?.params.id ?? 0)),
})
return runPerfSelectorComputation(number)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@benchmarks/client-nav/react/app.tsx` around lines 42 - 47, The Match
component is using useMatch without binding to the route that contains the id
param, so match.params.id is always undefined; update the useMatch call in
Match() to include the from option pointing to the '/$id' route (e.g.,
useMatch({ from: '/$id', strict: false, select: ... })) so it resolves the
correct route params before passing Number(match.params.id ?? 0) into
runPerfSelectorComputation.

}

function Links() {
Expand All @@ -60,6 +67,9 @@ function Root() {
{selectors.map((selector) => (
<Links key={selector} />
))}
{selectors.map((selector) => (
<Match key={selector} />
))}
<Outlet />
</>
)
Expand All @@ -72,7 +82,11 @@ const root = createRootRoute({
const route = createRoute({
getParentRoute: () => root,
path: '/$id',
validateSearch: (search) => ({ id: search.id }),
component: () => <div />,
beforeLoad: () => Promise.resolve(),
loaderDeps: ({ search }) => ({ id: search.id }),
loader: () => Promise.resolve(),
})

export function createTestRouter() {
Expand All @@ -82,6 +96,7 @@ export function createTestRouter() {
}),
scrollRestoration: true,
routeTree: root.addChildren([route]),
defaultPendingMinMs: 0,
})

const component = <RouterProvider router={router} />
Expand Down
7 changes: 5 additions & 2 deletions benchmarks/client-nav/react/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,13 @@ export function setup() {
next = () => {
const nextId = id++

// update param every 2 navigations,
// update search every other navigation,
// this way we can test the impact of both params and search updates,
// and still observe improvements on granular re-rendering
return navigate({
to: '/$id',
params: { id: nextId },
// update search every 2 navigations, to still test them, but also measure the impact of granular re-rendering
params: { id: Math.floor((nextId + 1) / 2) },
search: { id: Math.floor(nextId / 2) },
replace: true,
})
Expand Down
33 changes: 19 additions & 14 deletions benchmarks/client-nav/solid/app.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { For, createEffect } from 'solid-js'
import { For } from 'solid-js'
import {
Link,
Outlet,
Expand All @@ -7,14 +7,15 @@ import {
createRootRoute,
createRoute,
createRouter,
useMatch,
useParams,
useSearch,
} from '@tanstack/solid-router'

function runPerfSelectorComputation(seed: number) {
let value = Math.trunc(seed) | 0

for (let index = 0; index < 100; index++) {
for (let index = 0; index < 50; index++) {
value = (value * 1664525 + 1013904223 + index) >>> 0
}

Expand All @@ -24,29 +25,27 @@ function runPerfSelectorComputation(seed: number) {
const selectors = Array.from({ length: 20 }, (_, index) => index)

function Params() {
const params = useParams({
const number = useParams({
strict: false,
select: (params) => runPerfSelectorComputation(Number(params.id ?? 0)),
})

createEffect(() => {
void params()
})

return null
return <>{runPerfSelectorComputation(number())}</>
}

function Search() {
const search = useSearch({
const number = useSearch({
strict: false,
select: (search) => runPerfSelectorComputation(Number(search.id ?? 0)),
})
return <>{runPerfSelectorComputation(number())}</>
}

createEffect(() => {
void search()
function Match() {
const number = useMatch({
strict: false,
select: (match) => runPerfSelectorComputation(Number(match.params.id ?? 0)),
})

return null
return <>{runPerfSelectorComputation(number())}</>
}
Comment on lines +43 to 49
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n benchmarks/client-nav/solid/app.tsx | head -60

Repository: TanStack/router

Length of output: 1748


🌐 Web query:

TanStack Router useMatch strict false select callback documentation

💡 Result:

TanStack Router documents useMatch’s strict and select options on the useMatch hook API page. (tanstack.com)

strict: false

  • Default is true.
  • If strict is false, you must not pass from (it must be unset), and the returned match type is loosened (a Partial<RouteMatch>-style shape) to reflect that it could be any active match. (tanstack.com)
  • This is intended for shared/reusable components where you don’t know which route you’re under (also described in the Type Safety guide). (tanstack.com)

Example:

const match = useMatch({ strict: false })

(tanstack.com)

select: (match) => TSelected (the “select callback”)

  • Optional function called with the RouteMatch.
  • useMatch returns whatever your select returns.
  • The selected value is also used to decide whether to re-render (via shallow equality checks). (tanstack.com)

Example:

const pathname = useMatch({
  strict: false,
  select: (match) => match.pathname,
})

(Example pattern referenced in community discussion; core behavior is in the docs.) (tanstack.com)

Citations:


🏁 Script executed:

rg "strict.*false" benchmarks/client-nav/solid/app.tsx -A 3

Repository: TanStack/router

Length of output: 528


🌐 Web query:

TanStack Solid Router useMatch RouteMatch Partial type strict false

💡 Result:

In TanStack Router for Solid (@tanstack/solid-router), this is expected behavior:

  • useMatch is strict by default (strict: true). In strict mode you should pass from (a route id/path), and you get a fully-typed RouteMatch. (tanstack.com)
  • If you set strict: false, you must NOT pass from, and the return type becomes Partial<RouteMatch> (so properties like search are optional / possibly undefined). This is by design for reusable/shared components. (tanstack.com)

What to do

1) Route-specific component (fully typed)

const match = useMatch({ from: Route.fullPath }) // or Route.id
// match.search is fully typed

(tanstack.com)

2) Reusable component across many routes (no invariant, looser types)

const match = useMatch({ strict: false })
// match is Partial<RouteMatch>, so match.search may be undefined

(tanstack.com)

3) If you don’t want it to throw when a specific match isn’t active

const match = useMatch({ from: '/posts', shouldThrow: false }) // RouteMatch | undefined

(tanstack.com)

Citations:


🏁 Script executed:

# Search for type definitions or documentation in the repo
fd -e ts -e tsx | xargs rg "Partial.*RouteMatch" | head -20

Repository: TanStack/router

Length of output: 41


🏁 Script executed:

# Check for tsconfig and strict mode settings
fd -e json -name "tsconfig*" | xargs cat | grep -A 10 '"strict"' || echo "No strict setting found"

Repository: TanStack/router

Length of output: 330


🏁 Script executed:

# Check for any type errors or eslint configs
fd -e json -name ".eslintrc*" -o -name "tsconfig*" benchmarks/ | head -5

Repository: TanStack/router

Length of output: 289


🏁 Script executed:

# Look for type definition or implementation of useMatch in solid-router
fd "@tanstack/solid-router" -t d | head -3

Repository: TanStack/router

Length of output: 41


🏁 Script executed:

find . -name "tsconfig*.json" -type f 2>/dev/null | head -10

Repository: TanStack/router

Length of output: 435


🏁 Script executed:

# Look for TypeScript configuration
cat tsconfig.json 2>/dev/null | head -30 || echo "No root tsconfig found"

Repository: TanStack/router

Length of output: 846


🏁 Script executed:

# Check if there's a type definition file in node_modules or packages
find . -path "*/node_modules/@tanstack/solid-router*" -name "*.d.ts" 2>/dev/null | head -5 || echo "Type definitions not found in sandbox"

Repository: TanStack/router

Length of output: 41


🏁 Script executed:

# Search for type definitions within the repo itself
find . -path "*/packages/solid-router*" -type f \( -name "*.ts" -o -name "*.tsx" \) | grep -i "use-match\|useMatch" | head -10

Repository: TanStack/router

Length of output: 197


🏁 Script executed:

cat benchmarks/client-nav/solid/tsconfig.json

Repository: TanStack/router

Length of output: 315


🏁 Script executed:

cat packages/solid-router/src/useMatch.tsx

Repository: TanStack/router

Length of output: 3495


Use optional chaining to safely access match.params with strict: false.

With strict: false, the useMatch hook types the match parameter in the select callback as a partial/union type (MakeRouteMatchUnion), making the params property optional. Accessing match.params.id directly will fail TypeScript strict mode type checking. Use optional chaining to safely handle the potentially undefined params property:

 function Match() {
   const number = useMatch({
     strict: false,
-    select: (match) => runPerfSelectorComputation(Number(match.params.id ?? 0)),
+    select: (match) => runPerfSelectorComputation(Number(match?.params?.id ?? 0)),
   })
   return <>{runPerfSelectorComputation(number())}</>
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function Match() {
const number = useMatch({
strict: false,
select: (match) => runPerfSelectorComputation(Number(match.params.id ?? 0)),
})
return null
return <>{runPerfSelectorComputation(number())}</>
}
function Match() {
const number = useMatch({
strict: false,
select: (match) => runPerfSelectorComputation(Number(match?.params?.id ?? 0)),
})
return <>{runPerfSelectorComputation(number())}</>
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@benchmarks/client-nav/solid/app.tsx` around lines 43 - 49, The select
callback in Match is accessing match.params.id but with strict: false
match.params can be undefined; update the select to use optional chaining (e.g.
select: (match) => runPerfSelectorComputation(Number(match.params?.id ?? 0))) so
params is safely accessed, and ensure any other direct uses of match.params in
Match use the same match.params?.id pattern; keep using
runPerfSelectorComputation and the number() call as-is.


function Links() {
Expand All @@ -63,6 +62,7 @@ function Root() {
<For each={selectors}>{() => <Params />}</For>
<For each={selectors}>{() => <Search />}</For>
<For each={selectors}>{() => <Links />}</For>
<For each={selectors}>{() => <Match />}</For>
<Outlet />
</>
)
Expand All @@ -75,9 +75,13 @@ const root = createRootRoute({
const route = createRoute({
getParentRoute: () => root,
path: '/$id',
validateSearch: (search) => ({ id: search.id }),
component: () => {
return <div />
},
beforeLoad: () => Promise.resolve(),
loaderDeps: ({ search }) => ({ id: search.id }),
loader: () => Promise.resolve(),
})

export function createTestRouter() {
Expand All @@ -87,6 +91,7 @@ export function createTestRouter() {
}),
scrollRestoration: true,
routeTree: root.addChildren([route]),
defaultPendingMinMs: 0,
})

const component = () => <RouterProvider router={router} />
Expand Down
7 changes: 5 additions & 2 deletions benchmarks/client-nav/solid/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,13 @@ export function setup() {
next = () => {
const nextId = id++

// update param every 2 navigations,
// update search every other navigation,
// this way we can test the impact of both params and search updates,
// and still observe improvements on granular re-rendering
return navigate({
to: '/$id',
params: { id: nextId },
// update search every 2 navigations, to still test them, but also measure the impact of granular re-rendering
params: { id: Math.floor((nextId + 1) / 2) },
search: { id: Math.floor(nextId / 2) },
replace: true,
})
Expand Down
37 changes: 26 additions & 11 deletions benchmarks/client-nav/vue/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ import {
createRootRoute,
createRoute,
createRouter,
useMatch,
useParams,
useSearch,
} from '@tanstack/vue-router'

function runPerfSelectorComputation(seed: number) {
let value = Math.trunc(seed) | 0

for (let index = 0; index < 100; index++) {
for (let index = 0; index < 50; index++) {
value = (value * 1664525 + 1013904223 + index) >>> 0
}

Expand All @@ -25,29 +26,35 @@ const selectors = Array.from({ length: 20 }, (_, index) => index)

const Params = Vue.defineComponent({
setup() {
const params = useParams({
const number = useParams({
strict: false,
select: (params) => runPerfSelectorComputation(Number(params.id ?? 0)),
})

return () => {
void params.value
return null
}
return () => runPerfSelectorComputation(number.value)
},
})

const Search = Vue.defineComponent({
setup() {
const search = useSearch({
const number = useSearch({
strict: false,
select: (search) => runPerfSelectorComputation(Number(search.id ?? 0)),
})

return () => {
void search.value
return null
}
return () => runPerfSelectorComputation(number.value)
},
})

const Match = Vue.defineComponent({
setup() {
const number = useMatch({
strict: false,
select: (match) =>
runPerfSelectorComputation(Number(match.params.id ?? 0)),
})

return () => runPerfSelectorComputation(number.value)
},
})

Expand All @@ -74,6 +81,9 @@ const Root = Vue.defineComponent({
{selectors.map((selector) => (
<Links key={`link-${selector}`} />
))}
{selectors.map((selector) => (
<Match key={`match-${selector}`} />
))}
<Outlet />
</>
)
Expand All @@ -87,7 +97,11 @@ const root = createRootRoute({
const route = createRoute({
getParentRoute: () => root,
path: '/$id',
validateSearch: (search) => ({ id: search.id }),
component: () => <div />,
beforeLoad: () => Promise.resolve(),
loaderDeps: ({ search }) => ({ id: search.id }),
loader: () => Promise.resolve(),
Comment on lines +100 to +104
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Check if file exists and examine the code =="
if [ -f benchmarks/client-nav/vue/app.tsx ]; then
  echo "File exists. Showing lines 95-110:"
  sed -n '95,110p' benchmarks/client-nav/vue/app.tsx
else
  echo "File not found at benchmarks/client-nav/vue/app.tsx"
fi

echo
echo "== List files in benchmarks/client-nav =="
fd . benchmarks/client-nav --max-depth 2 -t f | head -20

Repository: TanStack/router

Length of output: 1372


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Check TypeScript strict mode settings =="
find . -name "tsconfig.json" -type f | head -10 | while read f; do
  echo "--- $f ---"
  jq '.compilerOptions | {strict, noImplicitAny, noImplicitThis}' "$f" 2>/dev/null || cat "$f"
done

Repository: TanStack/router

Length of output: 1281


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Search for validateSearch patterns =="
rg -n "validateSearch" benchmarks --type tsx --type ts -A 2 | head -50

Repository: TanStack/router

Length of output: 144


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Check root and parent tsconfigs =="
for f in ./tsconfig.json ./tsconfig.base.json ./benchmarks/tsconfig.json benchmarks/client-nav/tsconfig.json benchmarks/client-nav/vue/tsconfig.json; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    head -30 "$f"
    echo
  fi
done

echo "== Check if benchmarks/client-nav/vue/tsconfig.json extends anything =="
cat benchmarks/client-nav/vue/tsconfig.json

Repository: TanStack/router

Length of output: 1959


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Search for validateSearch in tsx files =="
rg -n "validateSearch" benchmarks/client-nav --type-list | head
rg -n "validateSearch" benchmarks/client-nav -t ts -t tsx 2>/dev/null | head -20 || find benchmarks/client-nav -name "*.tsx" -exec rg -n "validateSearch" {} +

Repository: TanStack/router

Length of output: 510


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Check what the search parameter type is =="
sed -n '1,110p' benchmarks/client-nav/vue/app.tsx | tail -50

Repository: TanStack/router

Length of output: 1258


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Find createRoute definition and validateSearch typing =="
find . -path ./node_modules -prune -o -name "*.ts" -o -name "*.tsx" | xargs rg -l "createRoute" | grep -E "(src|packages)" | head -5

echo
echo "== Search for other validateSearch usages in non-benchmark code =="
rg -n "validateSearch" src packages --type ts --type tsx -B 1 -A 1 2>/dev/null | head -60 || find . -path ./node_modules -prune -o -path ./benchmarks -prune -o \( -name "*.ts" -o -name "*.tsx" \) -exec rg -l "validateSearch" {} +

Repository: TanStack/router

Length of output: 367


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Check actual React benchmark for comparison =="
if [ -f benchmarks/client-nav/react/app.tsx ]; then
  echo "=== React benchmark validateSearch ==="
  rg -n "validateSearch" benchmarks/client-nav/react/app.tsx -B 2 -A 2
fi

echo
echo "== Check if there are type definitions or examples for validateSearch =="
rg -n "validateSearch.*search\|search.*validateSearch" packages src e2e --type ts --type tsx -A 3 2>/dev/null | head -80 || find . -path ./node_modules -prune -o \( -name "*.ts" -o -name "*.tsx" \) -exec rg "validateSearch" {} + | grep -v benchmarks | head -20

Repository: TanStack/router

Length of output: 3321


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Look at the e2e example that uses explicit typing =="
if [ -f e2e/vue-router/basic-file-based-jsx/src/routes/remountDeps.tsx ]; then
  rg -n "validateSearch" e2e/vue-router/basic-file-based-jsx/src/routes/remountDeps.tsx -B 2 -A 5
fi

echo
echo "== Check if TypeScript compilation would fail for the benchmark =="
echo "Looking for any build or type-check scripts..."
cat benchmarks/client-nav/package.json | grep -A 20 '"scripts"' || echo "No package.json found"

Repository: TanStack/router

Length of output: 2040


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Check if benchmarks are included in any type-checking or build =="
cat package.json | jq '.scripts' | head -30
echo
echo "== Look at what README says about benchmarks =="
head -50 benchmarks/client-nav/README.md

Repository: TanStack/router

Length of output: 3999


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Verify the exact pattern used in e2e tests =="
cat e2e/vue-router/basic-file-based-jsx/src/routes/remountDeps.tsx | sed -n '1,80p'

Repository: TanStack/router

Length of output: 548


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Check what happens when running type check on benchmarks =="
echo "Trying to type-check the vue benchmark..."
tsc -p benchmarks/client-nav/vue/tsconfig.json --noEmit 2>&1 | head -30 || true

echo
echo "== Check if the pattern in the code is correct given the input =="
echo "What type does TypeScript infer for validateSearch parameter?"
cat > /tmp/check.ts << 'EOF'
import { createRoute } from '@tanstack/router'

// Checking the implicit type of search parameter
const route = createRoute({
  path: '/$id',
  validateSearch: (search) => ({ id: search.id }),
})
EOF

tsc --noEmit --strict /tmp/check.ts 2>&1 | head -20 || echo "Type check completed"

Repository: TanStack/router

Length of output: 691


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Check if React benchmark has the same issue =="
rg -n "validateSearch" benchmarks/client-nav/react/app.tsx -B 1 -A 1

echo
echo "== Do both benchmarks have identical validateSearch patterns? =="
echo "Vue:"
rg -n "validateSearch:" benchmarks/client-nav/vue/app.tsx -A 1
echo
echo "React:"
rg -n "validateSearch:" benchmarks/client-nav/react/app.tsx -A 1

Repository: TanStack/router

Length of output: 463


Add explicit type annotation and narrowing to validateSearch for strict mode compliance.

The search parameter lacks explicit typing, which causes an implicit any type under strict mode. Align with e2e test patterns by annotating the parameter and narrowing the id value.

Suggested refactor
 const route = createRoute({
   getParentRoute: () => root,
   path: '/$id',
-  validateSearch: (search) => ({ id: search.id }),
+  validateSearch: (search: Record<string, unknown>) => ({
+    id: typeof search.id === 'string' ? search.id : '0',
+  }),
   component: () => <div />,
   beforeLoad: () => Promise.resolve(),
   loaderDeps: ({ search }) => ({ id: search.id }),
   loader: () => Promise.resolve(),
 })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
validateSearch: (search) => ({ id: search.id }),
component: () => <div />,
beforeLoad: () => Promise.resolve(),
loaderDeps: ({ search }) => ({ id: search.id }),
loader: () => Promise.resolve(),
validateSearch: (search: Record<string, unknown>) => ({
id: typeof search.id === 'string' ? search.id : '0',
}),
component: () => <div />,
beforeLoad: () => Promise.resolve(),
loaderDeps: ({ search }) => ({ id: search.id }),
loader: () => Promise.resolve(),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@benchmarks/client-nav/vue/app.tsx` around lines 100 - 104, The validateSearch
function lacks an explicit parameter type and must narrow the id for strict
mode; update validateSearch's signature to annotate its parameter (e.g.,
validateSearch: (search: { id?: unknown }) => ...) and perform a runtime
narrowing/validation of search.id (e.g., check typeof search.id === 'string' or
another expected type and throw or return a default if invalid) before returning
{ id: search.id } so the returned id has a concrete type; mirror the same
explicit typing/narrowing approach for loaderDeps: ({ search }) => ({ id:
search.id }) so both validateSearch and loaderDeps use the same declared search
shape and well-typed id.

})

export function createTestRouter() {
Expand All @@ -97,6 +111,7 @@ export function createTestRouter() {
}),
scrollRestoration: true,
routeTree: root.addChildren([route]),
defaultPendingMinMs: 0,
})

const component = <RouterProvider router={router} />
Expand Down
7 changes: 5 additions & 2 deletions benchmarks/client-nav/vue/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,13 @@ export function setup() {
next = () => {
const nextId = id++

// update param every 2 navigations,
// update search every other navigation,
// this way we can test the impact of both params and search updates,
// and still observe improvements on granular re-rendering
return navigate({
to: '/$id',
params: { id: nextId },
// update search every 2 navigations, to still test them, but also measure the impact of granular re-rendering
params: { id: Math.floor((nextId + 1) / 2) },
search: { id: Math.floor(nextId / 2) },
replace: true,
})
Expand Down
Loading