diff --git a/packages/docs/content/docs/server-side.mdx b/packages/docs/content/docs/server-side.mdx index 2ceb2220..bfd20833 100644 --- a/packages/docs/content/docs/server-side.mdx +++ b/packages/docs/content/docs/server-side.mdx @@ -3,8 +3,12 @@ title: Server-Side usage description: Type-safe search params on the server --- ++ This feature is available for Next.js only. + + If you wish to access the searchParams in a deeply nested Server Component -(ie: not in the Page component), you can use `createSearchParamsCache` +(ie: not in the Page component), you can use `createSearchParamsCache{:ts}` to do so in a type-safe manner.@@ -32,15 +36,16 @@ export const searchParamsCache = createSearchParamsCache({ ```tsx title="page.tsx" import { searchParamsCache } from './searchParams' +import { type SearchParams } from 'nuqs/server' + +type PageProps = { + searchParams: Promise // Next.js 15+: async searchParams prop +} -export default function Page({ - searchParams -}: { - searchParams: Record -}) { +export default async function Page({ searchParams }: PageProps) { // ⚠️ Don't forget to call `parse` here. // You can access type-safe values from the returned object: - const { q: query } = searchParamsCache.parse(searchParams) + const { q: query } = searchParamsCache.parse(await searchParams) return ( Search Results for {query}
@@ -81,8 +86,8 @@ import { coordinatesCache } from './searchParams' import { Server } from './server' import { Client } from './client' -export default function Page({ searchParams }) { - coordinatesCache.parse(searchParams) +export default async function Page({ searchParams }) { + coordinatesCache.parse(await searchParams) return ( <>diff --git a/packages/docs/content/docs/test.mdx b/packages/docs/content/docs/test.mdx deleted file mode 100644 index 5ea2d3e8..00000000 --- a/packages/docs/content/docs/test.mdx +++ /dev/null @@ -1,84 +0,0 @@ ---- -title: Test -description: Markdown sandbox ---- - -```tsx title="Short" -// [!code word:Test] -// [!code word:Page] -export default async function Page() { - return Test-} -``` - -```tsx title="Long" custom="compact" -// [!code word:Lorem] -Lorem deserunt dolore amet elit culpa mollit elit nostrud consequat qui nostrud - -Ex nisi do magna pariatur // [!code highlight] -Ipsum pariatur laborum et nisi et amet do amet commodo - -Nulla esse ullamco ea ullamco non mollit commodo sunt - -Consequat excepteur est irure incididunt pariatur sit nulla incididunt fugiat ipsum eiusmod ex Lorem aute. // [!code highlight:3] -Sunt excepteur deserunt ea ea ullamco do -Culpa voluptate anim consequat nisi aliquip Lorem occaecat excepteur aliquip non deserunt -Sit esse ex eiusmod quis ea minim adipisicing adipisicing mollit fugiat nulla dolore excepteur labore. - -Anim aliqua et occaecat deserunt cillum -Aliquip adipisicing duis magna reprehenderit -Elit aliquip fugiat excepteur excepteur aliquip eu pariatur officia sunt deserunt elit -Ex do occaecat veniam minim veniam cillum id deserunt ea in commodo voluptate ullamco ea reprehenderit -Et dolore aute dolore ullamco laborum elit Lorem. - -Reprehenderit ut sunt nisi magna quis do voluptate veniam occaecat esse incididunt excepteur adipisicing minim -Do ea sunt tempor adipisicing elit -Deserunt dolor adipisicing nostrud mollit duis ullamco ut nulla aliquip do -Nostrud in velit cillum do est ipsum -Ea cillum amet culpa quis proident excepteur eiusmod incididunt dolor sunt Lorem velit -Duis qui dolor eu -Deserunt consequat magna fugiat aliqua aute officia esse sit veniam consectetur excepteur do officia exercitation esse. - -Sunt exercitation amet sint voluptate cupidatat aliqua commodo velit irure -Anim in ex culpa ea culpa -Aute ad deserunt ex in culpa aliquip enim qui nisi consectetur ex exercitation duis occaecat adipisicing -Laboris elit non ut deserunt enim irure tempor fugiat ad eu magna dolore occaecat -Elit ullamco proident consequat labore eiusmod occaecat culpa duis dolore amet non minim labore -Aliqua laboris deserunt eiusmod culpa nisi laborum ut minim excepteur excepteur laboris tempor culpa. - -Sit minim magna id sint occaecat dolor dolore cupidatat anim nisi ut -Nulla reprehenderit mollit cupidatat ut labore magna officia deserunt -Esse est ullamco minim consectetur proident proident ex deserunt ipsum -Ea nostrud mollit ad incididunt enim consequat eiusmod commodo amet sunt. - -Irure reprehenderit nisi laboris esse occaecat anim cillum sunt anim officia proident -Commodo fugiat cupidatat eu aliquip exercitation tempor occaecat duis fugiat excepteur -Fugiat proident cillum pariatur amet in et -Magna labore culpa duis commodo qui reprehenderit dolore qui cillum in in pariatur -Eu sunt elit culpa amet nulla amet reprehenderit aliquip irure minim id tempor ullamco. - -Eu deserunt esse cillum sunt aute dolore eiusmod velit sint dolore ullamco sit duis proident ut -Amet sint reprehenderit exercitation labore ad officia sint aliquip ea -Sint est qui cupidatat amet consectetur ipsum labore dolor aute duis aliqua ut fugiat et -Dolor ullamco reprehenderit adipisicing laborum consequat officia aute nostrud qui ut aliqua velit velit -Dolore dolore cupidatat nisi aute officia cillum commodo. - -Proident quis reprehenderit incididunt sunt reprehenderit occaecat Lorem commodo aliquip -Mollit ea magna est ea ullamco do officia qui exercitation sint sunt -Eiusmod ex aliquip cupidatat qui nulla duis voluptate minim deserunt pariatur laborum eiusmod reprehenderit voluptate aliqua -Esse dolor commodo fugiat labore pariatur laboris sit amet incididunt. - -Eiusmod sint adipisicing voluptate qui nulla pariatur sit et magna ut -Laboris excepteur laboris Lorem elit nulla labore aliquip quis nisi -In qui est ut ea aliquip consequat est non aute -Eiusmod voluptate minim enim proident deserunt Lorem ullamco id. - -Incididunt reprehenderit ipsum cillum anim eiusmod magna esse Lorem dolore culpa nostrud -Proident ea qui nulla deserunt velit -Lorem pariatur ea elit veniam dolore qui et officia cupidatat ea ullamco mollit non culpa anim -Do duis mollit fugiat -Ad Lorem ipsum in laborum mollit -Occaecat sit deserunt magna culpa aliquip cupidatat -Incididunt sit nisi elit excepteur eiusmod fugiat anim id duis sit -Ullamco amet pariatur ea duis veniam ea mollit. -``` diff --git a/packages/docs/content/docs/testing.mdx b/packages/docs/content/docs/testing.mdx index 8d6f1d67..0401755f 100644 --- a/packages/docs/content/docs/testing.mdx +++ b/packages/docs/content/docs/testing.mdx @@ -3,11 +3,41 @@ title: Testing description: Some tips on testing components that use `nuqs` --- -Currently, the best way to test the behaviour of your components using -`useQueryState(s)` is end-to-end testing, with tools like Playwright or Cypress. +Since nuqs 2, you can unit-test components that use `useQueryState(s){:ts}` hooks +by wrapping your rendered component in a `NuqsTestingAdapter{:ts}`. -Running components that use the Next.js router in isolation requires mocking it, -which is being [worked on](https://github.com/scottrippey/next-router-mock/pull/103) -for the app router. +Here is an example for Vitest and Testing Library to test a button rendering +a counter: + +```tsx title="counter-button.test.tsx" +// [!code word:NuqsTestingAdapter] +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { NuqsTestingAdapter, type UrlUpdateEvent } from 'nuqs/adapters/testing' +import { describe, expect, it, vi } from 'vitest' +import { CounterButton } from './counter-button' + +it('should increment the count when clicked', async () => { + const user = userEvent.setup() + const onUrlUpdate = vi.fn<[UrlUpdateEvent]>() + render(, { + // 1. Setup the test by passing initial search params / querystring: + wrapper: ({ children }) => ( + + {children} + + ) + }) + // 2. Act + const button = screen.getByRole('button') + await user.click(button) + // 3. Assert changes in the state and in the (mocked) URL + expect(button).toHaveTextContent('count is 43') + expect(onUrlUpdate).toHaveBeenCalledOnce() + expect(onUrlUpdate.mock.calls[0][0].queryString).toBe('?count=43') + expect(onUrlUpdate.mock.calls[0][0].searchParams.get('count')).toBe('43') + expect(onUrlUpdate.mock.calls[0][0].options.history).toBe('push') +}) +``` See issue [#259](https://github.com/47ng/nuqs/issues/259) for more testing-related discussions. diff --git a/packages/docs/next.config.mjs b/packages/docs/next.config.mjs index c30e48fa..e887b022 100644 --- a/packages/docs/next.config.mjs +++ b/packages/docs/next.config.mjs @@ -27,6 +27,11 @@ const config = { destination: '/docs/installation', permanent: false }, + { + source: '/docs/parsers/community', + destination: '/docs/parsers/community/tanstack-table', + permanent: false + }, // Cool URLs don't break { source: '/docs/parsers', diff --git a/packages/docs/package.json b/packages/docs/package.json index fdde2bda..5bb56cac 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -3,6 +3,16 @@ "version": "0.0.0-internal", "private": true, "type": "module", + "author": { + "name": "François Best", + "email": "contact@francoisbest.com", + "url": "https://francoisbest.com" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/47ng/nuqs.git", + "directory": "packages/docs" + }, "scripts": { "dev": "next dev", "build": "next build", diff --git a/packages/docs/public/og/about.jpg b/packages/docs/public/og/about.jpg index 488c719a..ac0bf52d 100644 Binary files a/packages/docs/public/og/about.jpg and b/packages/docs/public/og/about.jpg differ diff --git a/packages/docs/public/og/adapters.jpg b/packages/docs/public/og/adapters.jpg new file mode 100644 index 00000000..2030b615 Binary files /dev/null and b/packages/docs/public/og/adapters.jpg differ diff --git a/packages/docs/public/og/basic-usage.jpg b/packages/docs/public/og/basic-usage.jpg index 0195669f..31d39d2a 100644 Binary files a/packages/docs/public/og/basic-usage.jpg and b/packages/docs/public/og/basic-usage.jpg differ diff --git a/packages/docs/public/og/batching.jpg b/packages/docs/public/og/batching.jpg index b94e01d8..f4b1366b 100644 Binary files a/packages/docs/public/og/batching.jpg and b/packages/docs/public/og/batching.jpg differ diff --git a/packages/docs/public/og/debugging.jpg b/packages/docs/public/og/debugging.jpg index e36174e0..0523cf92 100644 Binary files a/packages/docs/public/og/debugging.jpg and b/packages/docs/public/og/debugging.jpg differ diff --git a/packages/docs/public/og/installation.jpg b/packages/docs/public/og/installation.jpg index 4ed8aa02..3b6c5457 100644 Binary files a/packages/docs/public/og/installation.jpg and b/packages/docs/public/og/installation.jpg differ diff --git a/packages/docs/public/og/migrations/v2.jpg b/packages/docs/public/og/migrations/v2.jpg new file mode 100644 index 00000000..88475ee4 Binary files /dev/null and b/packages/docs/public/og/migrations/v2.jpg differ diff --git a/packages/docs/public/og/options.jpg b/packages/docs/public/og/options.jpg index ee6a1d93..fc9a7bd3 100644 Binary files a/packages/docs/public/og/options.jpg and b/packages/docs/public/og/options.jpg differ diff --git a/packages/docs/public/og/parsers.jpg b/packages/docs/public/og/parsers.jpg index 99e24ce5..a769e039 100644 Binary files a/packages/docs/public/og/parsers.jpg and b/packages/docs/public/og/parsers.jpg differ diff --git a/packages/docs/public/og/parsers/built-in.jpg b/packages/docs/public/og/parsers/built-in.jpg new file mode 100644 index 00000000..a769e039 Binary files /dev/null and b/packages/docs/public/og/parsers/built-in.jpg differ diff --git a/packages/docs/public/og/parsers/making-your-own.jpg b/packages/docs/public/og/parsers/making-your-own.jpg new file mode 100644 index 00000000..a7f7349e Binary files /dev/null and b/packages/docs/public/og/parsers/making-your-own.jpg differ diff --git a/packages/docs/public/og/seo.jpg b/packages/docs/public/og/seo.jpg index c7cbcfbd..d14566cd 100644 Binary files a/packages/docs/public/og/seo.jpg and b/packages/docs/public/og/seo.jpg differ diff --git a/packages/docs/public/og/server-side.jpg b/packages/docs/public/og/server-side.jpg index f28f456e..8d6815b3 100644 Binary files a/packages/docs/public/og/server-side.jpg and b/packages/docs/public/og/server-side.jpg differ diff --git a/packages/docs/public/og/testing.jpg b/packages/docs/public/og/testing.jpg index 89cd48cd..f9732f06 100644 Binary files a/packages/docs/public/og/testing.jpg and b/packages/docs/public/og/testing.jpg differ diff --git a/packages/docs/public/og/tips-tricks.jpg b/packages/docs/public/og/tips-tricks.jpg index ae8343b3..36246002 100644 Binary files a/packages/docs/public/og/tips-tricks.jpg and b/packages/docs/public/og/tips-tricks.jpg differ diff --git a/packages/docs/public/og/utilities.jpg b/packages/docs/public/og/utilities.jpg index d341a8df..e3f433c9 100644 Binary files a/packages/docs/public/og/utilities.jpg and b/packages/docs/public/og/utilities.jpg differ diff --git a/packages/docs/source.config.ts b/packages/docs/source.config.ts index f8d93f84..96428099 100644 --- a/packages/docs/source.config.ts +++ b/packages/docs/source.config.ts @@ -1,6 +1,12 @@ import { rehypeCode } from 'fumadocs-core/mdx-plugins' -import { defineConfig, defineDocs } from 'fumadocs-mdx/config' +import { + defineCollections, + defineConfig, + defineDocs, + frontmatterSchema +} from 'fumadocs-mdx/config' import remarkSmartypants from 'remark-smartypants' +import { z } from 'zod' import { rehypeCodeOptions } from './rehype-code.config' export default defineConfig({ @@ -13,3 +19,12 @@ export default defineConfig({ }) export const { docs, meta } = defineDocs() + +export const blog = defineCollections({ + dir: 'content/blog', + schema: frontmatterSchema.extend({ + author: z.string(), + date: z.string().date().or(z.date()).optional() + }), + type: 'doc' +}) diff --git a/packages/docs/src/app/(pages)/_landing/demo.tsx b/packages/docs/src/app/(pages)/_landing/demo.tsx index cfe87939..36ebcd50 100644 --- a/packages/docs/src/app/(pages)/_landing/demo.tsx +++ b/packages/docs/src/app/(pages)/_landing/demo.tsx @@ -14,7 +14,6 @@ export async function LandingDemo() { !line.includes('className="') && !line.includes('data-interacted=') ) .join('\n') - .replaceAll('next-usequerystate', 'nuqs') return ( <>) { } title="Universal" - description="Supports both the app router and pages router." + description="Supports Next.js (app & pages routers), React SPA, Remix, React Router, and more." + isNew /> } @@ -87,13 +88,11 @@ export function FeaturesSection(props: React.ComponentProps<'section'>) { icon={ } title="Server cache" description="Type-safe search params access in nested React Server Components. No prop drilling needed." - isNew /> } title="Transition" description="Support for useTransition to get loading states on server updates." - isNew /> } @@ -113,11 +112,11 @@ export function FeaturesSection(props: React.ComponentProps<'section'>) { icon={ } title={ - Tested + Tested & testable } - description="Tested against every Next.js release." + description="Tested against every Next.js release. Use the provided test adapter to test your components in isolation." /> ) diff --git a/packages/docs/src/app/(pages)/_landing/hero.tsx b/packages/docs/src/app/(pages)/_landing/hero.tsx index 1f4f2055..d523e8f7 100644 --- a/packages/docs/src/app/(pages)/_landing/hero.tsx +++ b/packages/docs/src/app/(pages)/_landing/hero.tsx @@ -15,7 +15,7 @@ export function HeroSection() { Type-safe search params
- state manager for Next.js + state manager for React