generated from chiffre-io/template-library
-
-
Notifications
You must be signed in to change notification settings - Fork 148
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add search params cache (#397)
* feat: Add search params cache This simplifies the use of parsers on the server, and allows accessing search params in a type-safe manner in deeply nested server components. Big thanks to @eric-burel for the tip on how to use React's `cache` this way. * doc: Add error Running out of relevant HTTP status codes.. Maybe this was a bad idea. * feat: Freeze cache after parsing and return it This allows accessing the parsed values straight from the page component. Freezing also ensures the object reference stays immutable for the whole duration of the page render. * chore: New API for seach param cache * test: Add cache e2e test & fix NUQS-500 * doc: Add cache feature * doc: App router support isn't new anymore * doc: Refactor cache code examples Cache can't be exported from the page file, so instead showcase the "soft convention" of using a searchParams.ts file next to the page. * chore: Ignore react on the server too * chore: Increase server size limit * test: Add more navigation cases to the cache test * chore: Add comment about the use of `cache` * chore: Change error to emphasize the Layouts case * chore: Remove children pages to get CI to pass What the actual ***? The mere presence of those pages (in the app router), and even if they only were to contain a static Hello world output, would cause the **pages** router to fail to apply updates, when there is a basePath, and on Next.js 13.5+. Losing sanity here trying to figure out what's wrong, and it's the middle of the night so I won't go deeper into this right now. See #397 (comment) * chore: Remove links * test: Restore faulty pages, but disabled * test: Fix gSSP in base test rig
- Loading branch information
Showing
22 changed files
with
531 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
# Empty Search Params Cache | ||
|
||
This error shows up on the server when trying to access a searchParam from | ||
a cache created with `createSearchParamsCache`, but when the cache was not | ||
properly populated at the top of the page. | ||
|
||
## A note on layouts | ||
|
||
The error can also occur if your server component consuming the search params | ||
cache is mounted in a **layout** component. Those don't receive search params as | ||
they are not re-rendered when the page renders. | ||
|
||
In this case, your only option is to turn the server component into a client | ||
component, and read the search params with `useQueryStates`. You can | ||
[feed it the same parser object](https://github.com/47ng/next-usequerystate#accessing-searchparams-in-server-components) | ||
you used to create the cache, and it you'll get the same | ||
type safety. | ||
|
||
## Possible Solution | ||
|
||
Run the `parseSearchParam` function on the page's `searchParams`: | ||
|
||
```tsx | ||
// page.tsx | ||
import { | ||
createSearchParamsCache, | ||
parseAsInteger, | ||
parseAsString | ||
} from 'next-usequerystate/parsers' | ||
|
||
const cache = createSearchParamsCache({ | ||
q: parseAsString, | ||
maxResults: parseAsInteger.withDefault(10) | ||
}) | ||
|
||
export default function Page({ | ||
searchParams | ||
}: { | ||
searchParams: Record<string, string | string[] | undefined> | ||
}) { | ||
// ⚠️ Don't forget to call `parse` here: | ||
const { q: query } = cache.parse(searchParams) | ||
return ( | ||
<div> | ||
<h1>Search Results for {query}</h1> | ||
<Results /> | ||
</div> | ||
) | ||
} | ||
|
||
function Results() { | ||
// In order to get search params from child server components: | ||
const maxResults = cache.get('maxResults') | ||
return <span>Showing up to {maxResults} results</span> | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# Search params cache already populated | ||
|
||
This error occurs when a [search params cache](https://github.com/47ng/next-usequerystate#accessing-searchparams-in-server-components) | ||
is being fed searchParams more than once. | ||
|
||
Internally, the cache object will be frozen for the duration of the page render | ||
after having been populated. This is to prevent search params from being modified | ||
while the page is being rendered. | ||
|
||
## Solutions | ||
|
||
Look into the stack trace where the error occurred and remove the second call to | ||
`parse` that threw the error. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
/// <reference types="cypress" /> | ||
|
||
describe('cache', () => { | ||
it('works in app router', () => { | ||
cy.visit('/app/cache?str=foo&num=42&bool=true') | ||
cy.get('#parse-str').should('have.text', 'foo') | ||
cy.get('#parse-num').should('have.text', '42') | ||
cy.get('#parse-bool').should('have.text', 'true') | ||
cy.get('#parse-def').should('have.text', 'default') | ||
cy.get('#parse-nope').should('have.text', 'null') | ||
cy.get('#all-str').should('have.text', 'foo') | ||
cy.get('#all-num').should('have.text', '42') | ||
cy.get('#all-bool').should('have.text', 'true') | ||
cy.get('#all-def').should('have.text', 'default') | ||
cy.get('#all-nope').should('have.text', 'null') | ||
cy.get('#get-str').should('have.text', 'foo') | ||
cy.get('#get-num').should('have.text', '42') | ||
cy.get('#get-bool').should('have.text', 'true') | ||
cy.get('#get-def').should('have.text', 'default') | ||
cy.get('#get-nope').should('have.text', 'null') | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import Link from 'next/link' | ||
import { Suspense } from 'react' | ||
import { All } from '../all' | ||
import { Get } from '../get' | ||
import { cache } from '../searchParams' | ||
import { Set } from '../set' | ||
|
||
export default function Page({ | ||
searchParams | ||
}: { | ||
searchParams: Record<string, string | string[] | undefined> | ||
}) { | ||
const { str, bool, num, def, nope } = cache.parse(searchParams) | ||
return ( | ||
<> | ||
<h1>Page A</h1> | ||
<h2>From parse:</h2> | ||
<p style={{ display: 'flex', gap: '1rem' }}> | ||
<span id="parse-str">{str}</span> | ||
<span id="parse-num">{num}</span> | ||
<span id="parse-bool">{String(bool)}</span> | ||
<span id="parse-def">{def}</span> | ||
<span id="parse-nope">{String(nope)}</span> | ||
</p> | ||
<All /> | ||
<Get /> | ||
<Suspense> | ||
<Set /> | ||
</Suspense> | ||
<ul> | ||
<li> | ||
<Link href="/app/cache?str=from-a">To root page</Link> | ||
</li> | ||
<li> | ||
<Link href="/app/cache/b?str=from-a">To page B</Link> | ||
</li> | ||
</ul> | ||
</> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { cache } from './searchParams' | ||
|
||
export function All() { | ||
const { bool, num, str, def, nope } = cache.all() | ||
return ( | ||
<> | ||
<h2>From all:</h2> | ||
<p style={{ display: 'flex', gap: '1rem' }}> | ||
<span id="all-str">{str}</span> | ||
<span id="all-num">{num}</span> | ||
<span id="all-bool">{String(bool)}</span> | ||
<span id="all-def">{def}</span> | ||
<span id="all-nope">{String(nope)}</span> | ||
</p> | ||
</> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import Link from 'next/link' | ||
import { Suspense } from 'react' | ||
import { All } from '../all' | ||
import { Get } from '../get' | ||
import { cache } from '../searchParams' | ||
import { Set } from '../set' | ||
|
||
export default function Page({ | ||
searchParams | ||
}: { | ||
searchParams: Record<string, string | string[] | undefined> | ||
}) { | ||
const { str, bool, num, def, nope } = cache.parse(searchParams) | ||
return ( | ||
<> | ||
<h1>Page B</h1> | ||
<h2>From parse:</h2> | ||
<p style={{ display: 'flex', gap: '1rem' }}> | ||
<span id="parse-str">{str}</span> | ||
<span id="parse-num">{num}</span> | ||
<span id="parse-bool">{String(bool)}</span> | ||
<span id="parse-def">{def}</span> | ||
<span id="parse-nope">{String(nope)}</span> | ||
</p> | ||
<All /> | ||
<Get /> | ||
<Suspense> | ||
<Set /> | ||
</Suspense> | ||
<ul> | ||
<li> | ||
<Link href="/app/cache?str=from-b">To root page</Link> | ||
</li> | ||
<li> | ||
<Link href="/app/cache/a?str=from-b">To page A</Link> | ||
</li> | ||
</ul> | ||
</> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { cache } from './searchParams' | ||
|
||
export function Get() { | ||
const bool = cache.get('bool') | ||
const num = cache.get('num') | ||
const str = cache.get('str') | ||
const def = cache.get('def') | ||
const nope = cache.get('nope') | ||
return ( | ||
<> | ||
<h2>From get:</h2> | ||
<p style={{ display: 'flex', gap: '1rem' }}> | ||
<span id="get-str">{str}</span> | ||
<span id="get-num">{num}</span> | ||
<span id="get-bool">{String(bool)}</span> | ||
<span id="get-def">{def}</span> | ||
<span id="get-nope">{String(nope)}</span> | ||
</p> | ||
</> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import React from 'react' | ||
import { cache } from './searchParams' | ||
|
||
export default function Layout({ children }: { children: React.ReactNode }) { | ||
let result = '' | ||
try { | ||
result = JSON.stringify(cache.all()) | ||
} catch (error) { | ||
result = String(error) | ||
} | ||
return ( | ||
<> | ||
{children} | ||
<h2>Layout</h2> | ||
<p id="layout-result">{result}</p> | ||
</> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { Suspense } from 'react' | ||
import { All } from './all' | ||
import { Get } from './get' | ||
import { cache } from './searchParams' | ||
import { Set } from './set' | ||
|
||
export default function Page({ | ||
searchParams | ||
}: { | ||
searchParams: Record<string, string | string[] | undefined> | ||
}) { | ||
const { str, bool, num, def, nope } = cache.parse(searchParams) | ||
return ( | ||
<> | ||
<h1>Root page</h1> | ||
<h2>From parse:</h2> | ||
<p style={{ display: 'flex', gap: '1rem' }}> | ||
<span id="parse-str">{str}</span> | ||
<span id="parse-num">{num}</span> | ||
<span id="parse-bool">{String(bool)}</span> | ||
<span id="parse-def">{def}</span> | ||
<span id="parse-nope">{String(nope)}</span> | ||
</p> | ||
<All /> | ||
<Get /> | ||
<Suspense> | ||
<Set /> | ||
</Suspense> | ||
</> | ||
) | ||
} |
Oops, something went wrong.