|
| 1 | +--- |
| 2 | +title: nuqs 2 |
| 3 | +description: Opening up to other React frameworks |
| 4 | +author: François Best |
| 5 | +date: 2024-10-22 |
| 6 | +--- |
| 7 | + |
| 8 | +nuqs@2.0.0 is available, try it now: |
| 9 | + |
| 10 | +```bash |
| 11 | +pnpm add nuqs@latest |
| 12 | +``` |
| 13 | + |
| 14 | +It's packing exciting features & improvements, including: |
| 15 | + |
| 16 | +- [Support for other React frameworks](#hello-react): Next.js, React SPA, Remix, React Router, and more to come |
| 17 | +- A built-in [testing adapter](#testing) to unit-test your components in isolation |
| 18 | +- [Bundle size improvements](#bundle-size-improvements) |
| 19 | +- Interactive documentation, with [community parsers](/docs/parsers/community) |
| 20 | + |
| 21 | +<hr/> |
| 22 | + |
| 23 | +## Hello, React! 👋 ⚛️ [#hello-react] |
| 24 | + |
| 25 | +nuqs started as a Next.js-only hook, and v2 brings compatibility for other React frameworks: |
| 26 | + |
| 27 | +- Next.js 14 & 15 (app & pages routers) |
| 28 | +- React SPA |
| 29 | +- Remix |
| 30 | +- React Router |
| 31 | + |
| 32 | +No code change is necessary in components that use nuqs hooks, |
| 33 | +making them **universal** across all supported frameworks. |
| 34 | + |
| 35 | +The only new requirement is to wrap your React tree with an |
| 36 | +[adapter](/docs/adapters) for your framework. |
| 37 | + |
| 38 | +Example for a React SPA with Vite: |
| 39 | + |
| 40 | +```tsx title="src/main.tsx" |
| 41 | +// [!code word:NuqsAdapter] |
| 42 | +import { NuqsAdapter } from 'nuqs/adapters/react' |
| 43 | + |
| 44 | +createRoot(document.getElementById('root')!).render( |
| 45 | + <NuqsAdapter> |
| 46 | + <App /> |
| 47 | + </NuqsAdapter> |
| 48 | +) |
| 49 | +``` |
| 50 | + |
| 51 | +<Callout> |
| 52 | + The [adapters documentation](/docs/adapters) has examples for all supported frameworks. |
| 53 | +</Callout> |
| 54 | + |
| 55 | +## Testing |
| 56 | + |
| 57 | +One of the major pain points with nuqs v1 was testing components that used its hooks. |
| 58 | + |
| 59 | +Nuqs v2 comes with a built-in [testing adapter](/docs/testing) that mocks URL behaviours, |
| 60 | +allowing you to test your components in isolation, outside of any framework runtime. |
| 61 | + |
| 62 | +You can use it with any unit testing framework that renders React components |
| 63 | +(I recommend [Vitest](https://vitest.dev) & [Testing Library](https://testing-library.com/)). |
| 64 | + |
| 65 | +```tsx title="counter-button.test.tsx" |
| 66 | +// [!code word:NuqsTestingAdapter] |
| 67 | +import { render, screen } from '@testing-library/react' |
| 68 | +import userEvent from '@testing-library/user-event' |
| 69 | +import { NuqsTestingAdapter, type UrlUpdateEvent } from 'nuqs/adapters/testing' |
| 70 | +import { describe, expect, it, vi } from 'vitest' |
| 71 | +import { CounterButton } from './counter-button' |
| 72 | + |
| 73 | +it('should increment the count when clicked', async () => { |
| 74 | + const user = userEvent.setup() |
| 75 | + const onUrlUpdate = vi.fn<[UrlUpdateEvent]>() |
| 76 | + render(<CounterButton />, { |
| 77 | + // 1. Setup the test by passing initial search params / querystring: |
| 78 | + wrapper: ({ children }) => ( |
| 79 | + <NuqsTestingAdapter searchParams="?count=42" onUrlUpdate={onUrlUpdate}> |
| 80 | + {children} |
| 81 | + </NuqsTestingAdapter> |
| 82 | + ) |
| 83 | + }) |
| 84 | + // 2. Act |
| 85 | + const button = screen.getByRole('button') |
| 86 | + await user.click(button) |
| 87 | + // 3. Assert changes in the state and in the (mocked) URL |
| 88 | + expect(button).toHaveTextContent('count is 43') |
| 89 | + expect(onUrlUpdate).toHaveBeenCalledOnce() |
| 90 | + expect(onUrlUpdate.mock.calls[0][0].queryString).toBe('?count=43') |
| 91 | + expect(onUrlUpdate.mock.calls[0][0].searchParams.get('count')).toBe('43') |
| 92 | + expect(onUrlUpdate.mock.calls[0][0].options.history).toBe('push') |
| 93 | +}) |
| 94 | +``` |
| 95 | + |
| 96 | +The adapter conforms to the **setup** / **act** / **assert** testing strategy, allowing you |
| 97 | +to: |
| 98 | + |
| 99 | +1. Set the initial URL search params |
| 100 | +2. Let your test framework perform actions on your component |
| 101 | +3. Asserting on how the URL was changed as a result |
| 102 | + |
| 103 | +## Breaking changes & migration |
| 104 | + |
| 105 | +The biggest breaking change is the introduction of [adapters](/docs/adapters). |
| 106 | +Another one is related to deprecated APIs. |
| 107 | + |
| 108 | +The `next-usequerystate` package that started this journey is no longer updated. |
| 109 | +All updates are now published under the `nuqs` package name. |
| 110 | + |
| 111 | +The minimum version of Next.js supported is now 14.2.0. It is compatible with |
| 112 | +Next.js 15, including the async `searchParams{:ts}` page prop in the [server-side cache](/docs/server-side). |
| 113 | + |
| 114 | +There are some important behaviour changes, based on feedback from the community: |
| 115 | + |
| 116 | +- [`clearOnDefault{:ts}`](/docs/options#clear-on-default) is now `true{:ts}` by default |
| 117 | +- [`startTransition{:ts}`](/docs/options#transitions) no longer sets `shallow: false{:ts}` |
| 118 | +- [`parseAsJson{:ts}`](/docs/parsers/built-in#json) now requires a validation function |
| 119 | + |
| 120 | +<Callout> |
| 121 | +Read the complete [migration guide](/docs/migrations/v2) to update your applications. |
| 122 | +</Callout> |
| 123 | + |
| 124 | +## Bundle size improvements |
| 125 | + |
| 126 | +By moving to **ESM-only**, and dropping hacks needed to support older versions of Next.js, |
| 127 | +the bundle size is now **20% smaller** than v1. It's also **side-effects free** and **tree-shakable**. |
| 128 | + |
| 129 | +## What's next? |
| 130 | + |
| 131 | +The community and I have a lot of ideas for the future of nuqs, including: |
| 132 | + |
| 133 | +- A unified, scalable, type-safe routing experience in all supported React frameworks |
| 134 | +- Community-contributed parsers & adapters |
| 135 | +- New options: debouncing, global defaults override |
| 136 | +- Middleware to migrate old URLs to new ones |
| 137 | +- Better Zod integration for type-safe & runtime-safe validation |
| 138 | + |
| 139 | +## Thanks |
| 140 | + |
| 141 | +I want to thank [sponsors](https://github.com/sponsors/franky47), |
| 142 | +[contributors](https://github.com/47ng/nuqs/graphs/contributors) |
| 143 | +and people who raised issues and discussions on |
| 144 | +[GitHub](https://github.com/47ng/nuqs) and [X/Twitter](https://x.com/nuqs47ng). |
| 145 | +You are the growing community that drives this project forward, |
| 146 | +and I couldn't be happier with the response. |
| 147 | + |
| 148 | +### Sponsors |
| 149 | + |
| 150 | +- [Pontus Abrahamsson](https://x.com/pontusab), founder of [Midday.ai](https://midday.ai) |
| 151 | +- [Carl Lindesvard](https://x.com/CarlLindesvard), founder of [OpenPanel](https://openpanel.dev) |
| 152 | +- [Robin Wieruch](https://x.com/rwieruch), author of [The Road to Next](https://www.road-to-next.com/) |
| 153 | +- [Yoann Fleury](https://x.com/YoannFleuryDev) |
| 154 | +- [Sunghyun Cho](https://github.com/anaclumos) |
| 155 | +- [Jalol](https://github.com/mirislomovmirjalol) |
| 156 | + |
| 157 | +Thanks to these amazing people, I'm able to dedicate more time to this project and make it better for everyone. |
| 158 | +Join them on [GitHub Sponsors](https://github.com/sponsors/franky47)! |
| 159 | + |
| 160 | +### Contributors |
| 161 | + |
| 162 | +Huge thanks to [@andreisocaciu](https://github.com/andreisocaciu), [@tordans](https://github.com/tordans), [@prasannamestha](https://github.com/prasannamestha), [@Talent30](https://github.com/Talent30), [@neefrehman](https://github.com/neefrehman), [@chbg](https://github.com/chbg), [@dopry](https://github.com/dopry), [@weisisheng](https://github.com/weisisheng), [@hugotiger](https://github.com/hugotiger), [@iuriizaporozhets](https://github.com/iuriizaporozhets), [@rikbrown](https://github.com/rikbrown), [@mateogianolio](https://github.com/mateogianolio), [@timheerwagen](https://github.com/timheerwagen), [@psdmsft](https://github.com/psdmsft), and [@psdewar](https://github.com/psdewar) for helping! |
0 commit comments