Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: Optimise render counts #849

Merged
merged 14 commits into from
Jan 8, 2025
Merged
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
57 changes: 57 additions & 0 deletions packages/e2e/next/cypress/e2e/shared/render-count.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { testRenderCount } from 'e2e-shared/specs/render-count.cy'

const hooks = ['useQueryState', 'useQueryStates'] as const
const shallows = [true, false] as const
const histories = ['replace', 'push'] as const

for (const hook of hooks) {
for (const shallow of shallows) {
for (const history of histories) {
for (const startTransition of [false, true]) {
for (const delay of shallow === false ? [0, 50] : [0]) {
testRenderCount({
path: `/app/render-count/${hook}/${shallow}/${history}/${startTransition}?delay=${delay}`,
hook,
props: {
shallow,
history,
startTransition,
delay
},
expected: {
mount: 1,
update: shallow === false ? 3 : 2
},
nextJsRouter: 'app'
})
}
}
}
}
}

for (const hook of hooks) {
for (const shallow of shallows) {
for (const history of histories) {
for (const startTransition of [false, true]) {
for (const delay of shallow === false ? [0, 50] : [0]) {
testRenderCount({
path: `/pages/render-count/${hook}/${shallow}/${history}/${startTransition}?delay=${delay}`,
hook,
props: {
shallow,
history,
startTransition,
delay
},
expected: {
mount: 1,
update: 2
},
nextJsRouter: 'pages'
})
}
}
}
}
}
2 changes: 1 addition & 1 deletion packages/e2e/next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"start": "NODE_OPTIONS='--enable-source-maps=true' next start --port 3001",
"pretest": "cypress install",
"test": "start-server-and-test start http://localhost:3001${BASE_PATH} cypress:run",
"cypress:open": "cypress open",
"cypress:open": "cypress open --e2e --browser electron",
"cypress:run": "cypress run --headless"
},
"dependencies": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { RenderCount } from 'e2e-shared/specs/render-count'
import {
loadParams,
loadSearchParams
} from 'e2e-shared/specs/render-count.params'
import { setTimeout } from 'node:timers/promises'
import { type SearchParams } from 'nuqs/server'
import { Suspense } from 'react'

export const dynamic = 'force-dynamic'

type PageProps = {
params: Promise<Record<keyof ReturnType<typeof loadParams>, string>>
searchParams: Promise<SearchParams>
}

export default async function Page({
params,
searchParams
}: PageProps & { searchParams: Promise<SearchParams> }) {
const { hook, shallow, history, startTransition } = await loadParams(params)
const { delay } = await loadSearchParams(searchParams)
if (delay) {
await setTimeout(delay)
}
return (
<Suspense>
<RenderCount
hook={hook}
shallow={shallow}
history={history}
startTransition={startTransition}
/>
</Suspense>
)
}

export async function generateStaticParams() {
const hooks = ['useQueryState', 'useQueryStates']
const shallow = [true, false]
const history = ['push', 'replace']
return hooks.flatMap(hook =>
shallow.flatMap(shallow =>
history.map(history => ({ hook, shallow: shallow.toString(), history }))
)
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { RenderCount } from 'e2e-shared/specs/render-count'
import {
loadParams,
loadSearchParams,
type Params
} from 'e2e-shared/specs/render-count.params'
import { GetServerSideProps } from 'next'
import { setTimeout } from 'node:timers/promises'

export default RenderCount

// We need SSR to get the correct initial render counts
// otherwise with SSG we get at least one extra render for hydration.
export const getServerSideProps: GetServerSideProps<Params> = async ({
params,
query
}) => {
const { delay } = loadSearchParams(query)
if (delay) {
await setTimeout(delay)
}
return {
props: loadParams(params!)
}
}
80 changes: 80 additions & 0 deletions packages/e2e/react-router/v6/cypress/e2e/shared/render-count.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { testRenderCount } from 'e2e-shared/specs/render-count.cy'

const hooks = ['useQueryState', 'useQueryStates'] as const
const shallows = [true, false] as const
const histories = ['replace', 'push'] as const

for (const hook of hooks) {
for (const shallow of shallows) {
for (const history of histories) {
for (const startTransition of [false, true]) {
testRenderCount({
path: `/render-count/${hook}/${shallow}/${history}/${startTransition}/no-loader`,
description: 'no loader',
hook,
props: {
shallow,
history,
startTransition
},
expected: {
mount: 1,
update: 2 + (shallow === false ? (startTransition ? 0 : 1) : 0)
}
})
}
}
}
}

for (const hook of hooks) {
for (const shallow of shallows) {
for (const history of histories) {
for (const startTransition of [false, true]) {
testRenderCount({
path: `/render-count/${hook}/${shallow}/${history}/${startTransition}/sync-loader`,
description: 'sync loader',
hook,
props: {
shallow,
history,
startTransition
},
expected: {
mount: 1,
update: 2 + (shallow === false ? 1 : 0)
}
})
}
}
}
}

for (const hook of hooks) {
for (const shallow of shallows) {
for (const history of histories) {
for (const startTransition of [false, true]) {
for (const delay of shallow === false ? [0, 50] : [0]) {
testRenderCount({
path: `/render-count/${hook}/${shallow}/${history}/${startTransition}/async-loader?delay=${delay}`,
description: 'async loader',
hook,
props: {
shallow,
history,
startTransition,
delay
},
expected: {
mount: 1,
update:
2 +
(shallow === false ? 1 : 0) +
(!startTransition && delay ? 1 : 0)
}
})
}
}
}
}
}
5 changes: 5 additions & 0 deletions packages/e2e/react-router/v6/src/react-router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ const router = createBrowserRouter(
<Route path="form/useQueryStates" lazy={load(import('./routes/form.useQueryStates'))} />
<Route path="referential-stability/useQueryState" lazy={load(import('./routes/referential-stability.useQueryState'))} />
<Route path="referential-stability/useQueryStates" lazy={load(import('./routes/referential-stability.useQueryStates'))} />

<Route path="render-count/:hook/:shallow/:history/:startTransition/no-loader" lazy={load(import('./routes/render-count.$hook.$shallow.$history.$startTransition.no-loader'))} />
<Route path="render-count/:hook/:shallow/:history/:startTransition/sync-loader" lazy={load(import('./routes/render-count.$hook.$shallow.$history.$startTransition.sync-loader'))} />
<Route path="render-count/:hook/:shallow/:history/:startTransition/async-loader" lazy={load(import('./routes/render-count.$hook.$shallow.$history.$startTransition.async-loader'))} />

{/* Reproductions */}
<Route path='repro-839' lazy={load(import('./routes/repro-839'))} />
</Route>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { RenderCount } from 'e2e-shared/specs/render-count'
import {
loadParams,
loadSearchParams
} from 'e2e-shared/specs/render-count.params'

import { useParams, type LoaderFunctionArgs } from 'react-router-dom'

const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))

export async function loader({ request }: LoaderFunctionArgs) {
const { delay } = loadSearchParams(request)
if (delay) {
await wait(delay)
}
return null
}

export default function Page() {
const params = loadParams(useParams())
return <RenderCount {...params} />
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { RenderCount } from 'e2e-shared/specs/render-count'
import { loadParams } from 'e2e-shared/specs/render-count.params'
import { useParams } from 'react-router-dom'

export default function Page() {
const params = loadParams(useParams())
return <RenderCount {...params} />
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { RenderCount } from 'e2e-shared/specs/render-count'
import { loadParams } from 'e2e-shared/specs/render-count.params'
import { useParams } from 'react-router-dom'

export function loader() {
return null
}

export default function Page() {
const params = loadParams(useParams())
return <RenderCount {...params} />
}
3 changes: 3 additions & 0 deletions packages/e2e/react-router/v7/app/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ export default [
route('/form/useQueryStates', './routes/form.useQueryStates.tsx'),
route('/referential-stability/useQueryState', './routes/referential-stability.useQueryState.tsx'),
route('/referential-stability/useQueryStates', './routes/referential-stability.useQueryStates.tsx'),
route('/render-count/:hook/:shallow/:history/:startTransition/no-loader', './routes/render-count.$hook.$shallow.$history.$startTransition.no-loader.tsx'),
route('/render-count/:hook/:shallow/:history/:startTransition/sync-loader', './routes/render-count.$hook.$shallow.$history.$startTransition.sync-loader.tsx'),
route('/render-count/:hook/:shallow/:history/:startTransition/async-loader', './routes/render-count.$hook.$shallow.$history.$startTransition.async-loader.tsx'),
// Reproductions
route('/repro-839', './routes/repro-839.tsx'),
])
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { RenderCount } from 'e2e-shared/specs/render-count'
import {
loadParams,
loadSearchParams
} from 'e2e-shared/specs/render-count.params'
import { setTimeout } from 'node:timers/promises'
import { useParams, type LoaderFunctionArgs } from 'react-router'

export async function loader({ request }: LoaderFunctionArgs) {
const { delay } = loadSearchParams(request)
if (delay) {
await setTimeout(delay)
}
return null
}

export default function Page() {
const params = loadParams(useParams())
return <RenderCount {...params} />
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { RenderCount } from 'e2e-shared/specs/render-count'
import { loadParams } from 'e2e-shared/specs/render-count.params'
import { useParams } from 'react-router'

export default function Page() {
const params = loadParams(useParams())
return <RenderCount {...params} />
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { RenderCount } from 'e2e-shared/specs/render-count'
import { loadParams } from 'e2e-shared/specs/render-count.params'
import { useParams } from 'react-router'

export function loader() {
return null
}

export default function Page() {
const params = loadParams(useParams())
return <RenderCount {...params} />
}
Loading
Loading