Skip to content
Open
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
120 changes: 120 additions & 0 deletions packages/react-router/tests/speed.bench.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { cleanup, render } from '@testing-library/react'
import { act } from 'react'
import { bench, describe } from 'vitest'
import { rootRouteId } from '@tanstack/router-core'
import {
Outlet,
RouterProvider,
createMemoryHistory,
createRootRoute,
createRoute,
createRouter,
useParams,
useSearch,
} from '../src'
import type { NavigateOptions } from '@tanstack/router-core'

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

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

return value
}

const selectors = Array.from({ length: 20 }, (_, index) => index)

function Params() {
const params = useParams({
from: rootRouteId,
select: (params) => runPerfSelectorComputation(Number(params.id ?? 0)),
Comment on lines +32 to +33

Choose a reason for hiding this comment

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

P2 Badge Benchmark params on matched route instead of root route

useParams here is subscribed with from: rootRouteId, but the root route in this test has no strict path params while the id param is defined on the child /$id route. In this setup params.id is always undefined, so runPerfSelectorComputation(Number(params.id ?? 0)) stays constant and these 20 Params selectors do not measure path-param recomputation during navigation, which skews the reported navigation-speed benchmark results.

Useful? React with 👍 / 👎.

})
void params
return null
}

function Search() {
const search = useSearch({
from: rootRouteId,
select: (search) => runPerfSelectorComputation(Number(search.id ?? 0)),
})
void search
return null
}
Comment on lines +30 to +46
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
# Explore the speed.bench.tsx file structure and context

# First, read the full benchmark file to understand context
echo "=== Full speed.bench.tsx file ===" 
cat packages/react-router/tests/speed.bench.tsx

Repository: TanStack/router

Length of output: 2737


🏁 Script executed:

#!/bin/bash
# Find the route definitions to understand rootRouteId and child route structure

echo "=== Looking for route definitions in tests directory ==="
rg -n "rootRouteId|createRootRoute|\$id" packages/react-router/tests/speed.bench.tsx -B 2 -A 2

echo "=== Checking if there's a routes file in the test directory ==="
fd -t f "route" packages/react-router/tests/ --max-depth 3

Repository: TanStack/router

Length of output: 1527


🏁 Script executed:

#!/bin/bash
# Check TypeScript configuration and compilation
echo "=== TypeScript config for react-router tests ==="
find packages/react-router -name "tsconfig*.json" -type f | head -5 | xargs -I {} sh -c 'echo "File: {}"; cat {}'

echo "=== Check if this file type is included in type checking ==="
rg -n "bench.tsx|speed.bench" packages/react-router/tsconfig* 2>/dev/null || echo "Pattern not found in tsconfig files"

Repository: TanStack/router

Length of output: 543


🏁 Script executed:

#!/bin/bash
# Look for similar useParams/useSearch patterns with from parameter in test files
echo "=== Similar useParams/useSearch patterns in tests ==="
rg -n "useParams|useSearch" packages/react-router/tests/ -A 2 | head -50

Repository: TanStack/router

Length of output: 3662


🏁 Script executed:

#!/bin/bash
# Check if there are any type checking configurations that might exclude bench files
echo "=== Checking package.json for type checking scripts ==="
cat packages/react-router/package.json | grep -A 5 '"type\|"test\|"check' | head -30

echo "=== Looking for any tsconfig exclusions ==="
cat packages/react-router/tsconfig.json | grep -i exclude

Repository: TanStack/router

Length of output: 1475


🏁 Script executed:

#!/bin/bash
# Check the useParams implementation to understand how select function is typed
echo "=== Finding useParams hook implementation ==="
fd -t f "useParams" packages/react-router/src --max-depth 2

echo "=== Examining useParams implementation ==="
rg -n "export.*useParams|function useParams" packages/react-router/src -A 20 | head -80

Repository: TanStack/router

Length of output: 3131


🏁 Script executed:

#!/bin/bash
# Look for type definitions to understand select function parameter typing
echo "=== Looking for useParams type definitions ==="
fd -t f "\.d\.ts" packages/react-router/src --max-depth 3 | xargs grep -l "useParams" 2>/dev/null | head -3

echo "=== Checking if select function has implicit any ==="
ast-grep --pattern 'select: ($_) => $$$'

Repository: TanStack/router

Length of output: 158


🏁 Script executed:

#!/bin/bash
# Check the UseParamsOptions type definition to understand select parameter typing
echo "=== Looking for UseParamsOptions type definition ==="
rg -n "UseParamsOptions|type UseParamsOptions" packages/react-router/src -A 10 | head -60

echo "=== Checking typePrimitives file for ValidateUseParamsOptions ==="
cat packages/react-router/src/typePrimitives.ts | grep -A 30 "ValidateUseParamsOptions" | head -50

Repository: TanStack/router

Length of output: 4651


🏁 Script executed:

#!/bin/bash
# Verify which tsconfig is actually used for type checking
echo "=== Content of tsconfig.legacy.json ==="
cat packages/react-router/tsconfig.legacy.json

echo ""
echo "=== Checking root tsconfig for defaults ==="
cat tsconfig.json | head -40

Repository: TanStack/router

Length of output: 988


🏁 Script executed:

#!/bin/bash
# Verify actual TypeScript compilation would catch this
echo "=== Trying to compile the bench file with TypeScript ==="
# Check if the file would have type errors by looking at its structure
echo "Looking at line 33 specifically where params.id is accessed:"
sed -n '30,35p' packages/react-router/tests/speed.bench.tsx

echo ""
echo "Root route definition (no params):"
sed -n '62,64p' packages/react-router/tests/speed.bench.tsx

echo ""
echo "Child route with \$id param:"
sed -n '66,70p' packages/react-router/tests/speed.bench.tsx

Repository: TanStack/router

Length of output: 594


🏁 Script executed:

#!/bin/bash
# Check the UseParamsBaseOptions to understand the select function parameter type
echo "=== Looking for UseParamsBaseOptions definition ==="
rg -n "UseParamsBaseOptions|type UseParamsBaseOptions" packages/react-router/src -A 15 | head -80

echo ""
echo "=== Check if select parameter has explicit type ==="
rg -n "select.*\?:" packages/react-router/src/useParams.tsx -B 2 -A 2

Repository: TanStack/router

Length of output: 3532


🏁 Script executed:

#!/bin/bash
# Confirm that test files are NOT actually type-checked by the test:types script
echo "=== Verifying type checking excludes test files ==="
echo "The test:types script uses tsconfig.legacy.json:"
cat packages/react-router/package.json | grep -A 10 '"test:types"'

echo ""
echo "And tsconfig.legacy.json only includes src:"
cat packages/react-router/tsconfig.legacy.json

echo ""
echo "But packages/react-router/tsconfig.json includes tests:"
cat packages/react-router/tsconfig.json | grep -A 5 '"include"'

Repository: TanStack/router

Length of output: 1192


🏁 Script executed:

#!/bin/bash
# Check how ResolveUseParams resolves the type when from: rootRouteId is used
echo "=== Looking for ResolveUseParams type definition ==="
rg -n "ResolveUseParams|type ResolveUseParams" packages/react-router/src -A 15 | head -100

echo ""
echo "=== Check if there's special handling for from parameter ==="
rg -n "from.*rootRouteId|resolveFrom" packages/router-core/src -A 5 | head -40

Repository: TanStack/router

Length of output: 2275


🏁 Script executed:

#!/bin/bash
# Double-check: is this file actually compiled in vitest or just interpreted?
echo "=== Checking vitest/tsconfig behavior for .tsx files ==="
cat packages/react-router/vite.config.ts 2>/dev/null | head -50 || echo "Checking for vitest config..."

echo ""
echo "=== Confirm benchmark file is intended for performance testing only ==="
head -30 packages/react-router/tests/speed.bench.tsx

Repository: TanStack/router

Length of output: 1858


🏁 Script executed:

#!/bin/bash
# The key finding: vite.config.ts shows typecheck is enabled!
# This means the bench file IS type-checked. Let's check ResolveUseParams in router-core
echo "=== Checking ResolveUseParams definition in router-core ==="
fd -t f "ResolveUseParams" packages/router-core/src --max-depth 3

echo ""
echo "=== Finding the actual implementation ==="
rg -n "type ResolveUseParams|ResolveUseParams =" packages/router-core/src -A 10 | head -80

Repository: TanStack/router

Length of output: 905


🏁 Script executed:

#!/bin/bash
# Check if rootRouteId has special typing behavior
echo "=== Checking rootRouteId definition ==="
rg -n "rootRouteId|export.*rootRouteId" packages/router-core/src -B 2 -A 5 | head -40

echo ""
echo "=== Checking how from parameter affects type resolution ==="
rg -n "from.*string.*undefined|TFrom extends" packages/router-core/src -B 2 -A 2 | head -50

Repository: TanStack/router

Length of output: 5750


🏁 Script executed:

#!/bin/bash
# Check the actual rootRouteId constant definition
echo "=== Finding rootRouteId constant ==="
rg -n "export.*rootRouteId|const rootRouteId" packages/router-core/src -B 2 -A 5

echo ""
echo "=== Check RouteById type to understand how it resolves ==="
rg -n "type RouteById|RouteById<" packages/router-core/src -A 8 | head -60

Repository: TanStack/router

Length of output: 4993


🏁 Script executed:

#!/bin/bash
# Most important: verify if type checking actually catches this as an error
echo "=== Try running the actual typecheck on this file ==="
cd packages/react-router
echo "Running vitest typecheck on speed.bench.tsx:"
npx vitest typecheck packages/react-router/tests/speed.bench.tsx 2>&1 || echo "Exit code: $?"

Repository: TanStack/router

Length of output: 1148


🏁 Script executed:

#!/bin/bash
# Verify final key details about type checking
echo "=== Verify: Does tsconfig.legacy.json (used by test:types) include bench files? ==="
cat packages/react-router/tsconfig.legacy.json

echo ""
echo "=== Verify: Does vite.config.ts enable typecheck for bench files? ==="
cat packages/react-router/vite.config.ts | grep -A 5 "typecheck"

echo ""
echo "=== Summary: Check what RoutesById contains for root route ==="
rg -n "type RoutesById|RoutesById<" packages/router-core/src -A 5 | head -40

Repository: TanStack/router

Length of output: 2781


🏁 Script executed:

#!/bin/bash
# Final verification: Can we understand what allParams resolves to for root route?
echo "=== Looking at ParseRoute and how allParams is built ==="
rg -n "allParams|'types'" packages/router-core/src -B 2 -A 2 | head -80

Repository: TanStack/router

Length of output: 5641


🏁 Script executed:

#!/bin/bash
# Final verification: Check if there are comments or patterns suggesting this is intentional
echo "=== Check context around the Params/Search components ==="
sed -n '28,50p' packages/react-router/tests/speed.bench.tsx

echo ""
echo "=== Check if void suppression is common pattern in benchmarks ==="
rg -n "void params|void search" packages/react-router/tests/ -B 2 -A 2

echo ""
echo "=== Understand the benchmark purpose ==="
sed -n '88,110p' packages/react-router/tests/speed.bench.tsx

Repository: TanStack/router

Length of output: 2038


Use from: '/$id' for accessing the id parameter in select functions.

Using from: rootRouteId in useParams and useSearch to access id (defined on the /$id route) is imprecise. The root route defines no params, so the select function's parameter type infers to {}. While the code compiles due to flexible select function typing, using from: '/$id' would correctly reference the route where id is actually defined and improve type safety.

function Params() {
  const params = useParams({
    from: '/$id',
    select: (params) => runPerfSelectorComputation(Number(params.id ?? 0)),
  })
  void params
  return null
}

function Search() {
  const search = useSearch({
    from: '/$id',
    select: (search) => runPerfSelectorComputation(Number(search.id ?? 0)),
  })
  void search
  return null
}

This aligns with TypeScript strict mode requirements and makes the code's intent clearer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react-router/tests/speed.bench.tsx` around lines 30 - 46, The Params
and Search components currently call useParams and useSearch with from:
rootRouteId which doesn't target the route that defines the id param; change the
from option to the concrete route id '/$id' in both Params and Search so the
select functions receive the correct param types (update the useParams call in
function Params and the useSearch call in function Search to use from: '/$id').


function Root() {
return (
<>
{selectors.map((selector) => (
<Params key={selector} />
))}
{selectors.map((selector) => (
<Search key={selector} />
))}
<Outlet />
</>
)
}

const root = createRootRoute({
component: Root,
})

const route = createRoute({
getParentRoute: () => root,
path: '/$id',
component: () => <div />,
})

return createRouter({
history: createMemoryHistory({
initialEntries: ['/0'],
}),
scrollRestoration: true,
routeTree: root.addChildren([route]),
})
}

describe('speed', () => {
let id = 0
const router = createTestRouter()
let unsub = () => {}
let next: () => Promise<void>

bench('navigate', () => act(next), {
warmupIterations: 1000,
time: 10_000,
setup: async () => {
id = 0
let resolve = () => {}
unsub = router.subscribe('onRendered', () => resolve())

const navigate = (opts: NavigateOptions) =>
new Promise<void>((resolveNext, rejectNext) => {
resolve = resolveNext
router.navigate(opts).catch(rejectNext)
})

next = () => {
const nextId = id++

return navigate({
to: '/$id',
params: { id: nextId },
search: { id: nextId },
replace: true,
})
}

render(<RouterProvider router={router} />)
await act(() => router.load())
},
teardown: () => {
cleanup()
unsub()
},
})
})
Loading