diff --git a/packages/docs/src/app/(pages)/playground/_components/query-spy.tsx b/packages/docs/src/app/(pages)/playground/_components/query-spy.tsx index fd4c15bc5..bbfc5f8c2 100644 --- a/packages/docs/src/app/(pages)/playground/_components/query-spy.tsx +++ b/packages/docs/src/app/(pages)/playground/_components/query-spy.tsx @@ -1,32 +1,11 @@ 'use client' import { useSearchParams } from 'next/navigation' -import { subscribeToQueryUpdates } from 'nuqs' import React from 'react' export const QuerySpy: React.FC = () => { const initialSearchParams = useSearchParams() - const [search, setSearch] = React.useState(() => { - if (typeof location !== 'object') { - // SSR - const out = new URLSearchParams() - if (!initialSearchParams) { - return out - } - for (const [key, value] of initialSearchParams) { - out.set(key, value) - } - return out - } else { - return new URLSearchParams(location.search) - } - }) - - React.useLayoutEffect( - () => subscribeToQueryUpdates(({ search }) => setSearch(search)), - [] - ) - const qs = search.toString() + const qs = initialSearchParams?.toString() return (
 {
-    const off = subscribeToQueryUpdates(({ search }) =>
-      console.log(search.toString())
-    )
-    return off
-  }, [])
-
-  return (
-    <>
-      

Subscribing to query updates

- - - -

{counter}

-

- Check the console -

-

- - Source on GitHub - -

- - ) -} diff --git a/packages/e2e/src/pages/pages/useQueryState/index.tsx b/packages/e2e/src/pages/pages/useQueryState/index.tsx index 3142c2fcf..ecb17f993 100644 --- a/packages/e2e/src/pages/pages/useQueryState/index.tsx +++ b/packages/e2e/src/pages/pages/useQueryState/index.tsx @@ -1,7 +1,13 @@ import { GetServerSideProps } from 'next' import Link from 'next/link' import { usePathname } from 'next/navigation' -import { parseAsString, queryTypes, useQueryState } from 'nuqs' +import { + parseAsBoolean, + parseAsFloat, + parseAsInteger, + parseAsString, + useQueryState +} from 'nuqs' import { HydrationMarker } from '../../../components/hydration-marker' export const getServerSideProps = (async ctx => { @@ -18,12 +24,12 @@ export const getServerSideProps = (async ctx => { const IntegrationPage = () => { const [string, setString] = useQueryState('string') - const [int, setInt] = useQueryState('int', queryTypes.integer) - const [float, setFloat] = useQueryState('float', queryTypes.float) - const [bool, setBool] = useQueryState('bool', queryTypes.boolean) + const [int, setInt] = useQueryState('int', parseAsInteger) + const [float, setFloat] = useQueryState('float', parseAsFloat) + const [bool, setBool] = useQueryState('bool', parseAsBoolean) const [text, setText] = useQueryState( 'text', - queryTypes.string.withDefault('Hello, world!') + parseAsString.withDefault('Hello, world!') ) const pathname = usePathname() return ( diff --git a/packages/e2e/src/pages/pages/useQueryStates/index.tsx b/packages/e2e/src/pages/pages/useQueryStates/index.tsx index 990c6d23c..17d2357d2 100644 --- a/packages/e2e/src/pages/pages/useQueryStates/index.tsx +++ b/packages/e2e/src/pages/pages/useQueryStates/index.tsx @@ -1,13 +1,19 @@ import Link from 'next/link' -import { queryTypes, useQueryStates } from 'nuqs' +import { + parseAsBoolean, + parseAsFloat, + parseAsInteger, + parseAsString, + useQueryStates +} from 'nuqs' import { HydrationMarker } from '../../../components/hydration-marker' const IntegrationPage = () => { const [state, setState] = useQueryStates({ - string: queryTypes.string, - int: queryTypes.integer, - float: queryTypes.float, - bool: queryTypes.boolean + string: parseAsString, + int: parseAsInteger, + float: parseAsFloat, + bool: parseAsBoolean }) return ( <> diff --git a/packages/nuqs/src/deprecated.ts b/packages/nuqs/src/deprecated.ts deleted file mode 100644 index d9583c695..000000000 --- a/packages/nuqs/src/deprecated.ts +++ /dev/null @@ -1,70 +0,0 @@ -import type { Parser, ParserBuilder } from './parsers' -import { - parseAsArrayOf, - parseAsBoolean, - parseAsFloat, - parseAsInteger, - parseAsIsoDateTime, - parseAsJson, - parseAsString, - parseAsStringEnum, - parseAsTimestamp -} from './parsers' - -/** - * @deprecated renamed to Parser - */ -export type Serializers = Parser - -/** - * @deprecated renamed to ParserBuilder. - * You should probably use `createParser` instead. - */ -export type SerializersWithDefaultFactory = ParserBuilder - -/** - * @deprecated use individual `parseAsXyz` imports instead. - */ -export const queryTypes = { - /** - * @deprecated use `parseAsString` instead. - */ - string: parseAsString, - /** - * @deprecated use `parseAsInteger` instead. - */ - integer: parseAsInteger, - /** - * @deprecated use `parseAsFloat` instead. - */ - float: parseAsFloat, - /** - * @deprecated use `parseAsBoolean` instead. - */ - boolean: parseAsBoolean, - /** - * @deprecated use `parseAsTimestamp` instead. - */ - timestamp: parseAsTimestamp, - /** - * @deprecated use `parseAsIsoDateTime` instead. - */ - isoDateTime: parseAsIsoDateTime, - /** - * @deprecated use `parseAsStringEnum` instead. - */ - stringEnum: parseAsStringEnum, - /** - * @deprecated use `parseAsJson` instead. - */ - json: parseAsJson, - /** - * @deprecated use `parseAsArrayOf` instead. - */ - array: parseAsArrayOf -} as const - -/** - * @deprecated use individual `parseAsXyz` imports instead - */ -export type QueryTypeMap = typeof queryTypes diff --git a/packages/nuqs/src/index.ts b/packages/nuqs/src/index.ts index 01b1e0e89..3cb057faf 100644 --- a/packages/nuqs/src/index.ts +++ b/packages/nuqs/src/index.ts @@ -1,9 +1,7 @@ 'use client' export type { HistoryOptions, Options } from './defs' -export * from './deprecated' export * from './parsers' -export { subscribeToQueryUpdates } from './sync' export type { QueryUpdateNotificationArgs, QueryUpdateSource } from './sync' export * from './useQueryState' export * from './useQueryStates' diff --git a/packages/nuqs/src/sync.ts b/packages/nuqs/src/sync.ts index 685706c4d..a961fa0ce 100644 --- a/packages/nuqs/src/sync.ts +++ b/packages/nuqs/src/sync.ts @@ -27,21 +27,6 @@ declare global { } } -/** - * @deprecated Since Next.js introduced shallow routing in 14.0.3, this - * method is no longer needed as you can use `useSearchParams`, which will - * react to changes in the URL when the `windowHistorySupport` experimental flag - * is set. - * This method will be removed in `nuqs@2.0.0`, when Next.js - * decides to land the `windowHistorySupport` flag in GA. - */ -export function subscribeToQueryUpdates( - callback: (args: QueryUpdateNotificationArgs) => void -) { - emitter.on(NOTIFY_EVENT_KEY, callback) - return () => emitter.off(NOTIFY_EVENT_KEY, callback) -} - if (typeof history === 'object') { patchHistory() } diff --git a/packages/nuqs/src/tests/compat/useQueryState.test-d.ts b/packages/nuqs/src/tests/compat/useQueryState.test-d.ts deleted file mode 100644 index 30d93dfa5..000000000 --- a/packages/nuqs/src/tests/compat/useQueryState.test-d.ts +++ /dev/null @@ -1,257 +0,0 @@ -import { expectError, expectNotAssignable, expectType } from 'tsd' -import { queryTypes, useQueryState } from '../../../dist' - -// By default, queries have a `string` state, nullable (when no query parameter is present) -{ - const [state, setState] = useQueryState('foo') - expectType(state) - setState('bar') - setState(old => old?.toUpperCase() ?? null) - const search = await setState('bar') - expectType(search) -} - -// Accept only a single `history` option -{ - const [state, setState] = useQueryState('foo', { history: 'push' }) - expectType(state) - setState('bar') - setState(old => old?.toUpperCase() ?? null) - const search = await setState('bar') - expectType(search) -} - -// Supported query types -{ - const [state] = useQueryState('string', queryTypes.string) - expectType(state) -} -{ - const [state] = useQueryState('integer', queryTypes.integer) - expectType(state) -} -{ - const [state] = useQueryState('float', queryTypes.float) - expectType(state) -} -{ - const [state] = useQueryState('boolean', queryTypes.boolean) - expectType(state) -} -{ - const [state] = useQueryState('boolean', queryTypes.timestamp) - expectType(state) -} -{ - const [state] = useQueryState('boolean', queryTypes.isoDateTime) - expectType(state) -} - -// With default values, state is no longer nullable -{ - const [state] = useQueryState('string', queryTypes.string.withDefault('foo')) - expectType(state) - expectNotAssignable(state) -} -{ - const [state] = useQueryState('integer', queryTypes.integer.withDefault(0)) - expectType(state) - expectNotAssignable(state) -} -{ - const [state] = useQueryState('float', queryTypes.float.withDefault(0)) - expectType(state) - expectNotAssignable(state) -} -{ - const [state] = useQueryState( - 'boolean', - queryTypes.boolean.withDefault(false) - ) - expectType(state) - expectNotAssignable(state) -} -{ - const [state] = useQueryState( - 'boolean', - queryTypes.timestamp.withDefault(new Date()) - ) - expectType(state) - expectNotAssignable(state) -} -{ - const [state] = useQueryState( - 'boolean', - queryTypes.isoDateTime.withDefault(new Date()) - ) - expectType(state) - expectNotAssignable(state) -} - -// Default value can be spread in: -{ - const [state] = useQueryState('string', { - ...queryTypes.string, - defaultValue: 'foo' - }) - expectType(state) - expectNotAssignable(state) -} -{ - const [state] = useQueryState('integer', { - ...queryTypes.integer, - defaultValue: 0 - }) - expectType(state) - expectNotAssignable(state) -} -{ - const [state] = useQueryState('float', { - ...queryTypes.float, - defaultValue: 0 - }) - expectType(state) - expectNotAssignable(state) -} -{ - const [state] = useQueryState('boolean', { - ...queryTypes.boolean, - defaultValue: false - }) - expectType(state) - expectNotAssignable(state) -} -{ - const [state] = useQueryState('boolean', { - ...queryTypes.timestamp, - defaultValue: new Date() - }) - expectType(state) - expectNotAssignable(state) -} -{ - const [state] = useQueryState('boolean', { - ...queryTypes.isoDateTime, - defaultValue: new Date() - }) - expectType(state) - expectNotAssignable(state) -} - -// Custom serializers -- -{ - const [hex] = useQueryState('foo', { - parse: input => parseInt(input, 16) - }) - expectType(hex) -} -{ - const [num] = useQueryState('foo', { - parse: parseInt, - serialize: value => value.toString(16) - }) - expectType(num) - - const [hex] = useQueryState('foo', { - parse: (input: string) => parseInt(input, 16), - serialize: value => value.toString(16) - }) - expectType(hex) - - const [len] = useQueryState('length', { - parse: (input: string) => input.length, - serialize: value => Array.from({ length: value }, () => '•').join('') - }) - expectType(len) -} -{ - const [hex] = useQueryState('foo', { - parse: input => parseInt(input, 16), - serialize: value => value.toString(16), - defaultValue: 0x2a - }) - expectType(hex) - expectNotAssignable(hex) -} -{ - const [hex] = useQueryState('foo', { - parse: input => parseInt(input, 16), - defaultValue: 0x2a - }) - expectType(hex) - expectNotAssignable(hex) -} - -// Allow setting `null` to clear the query -{ - const [, set] = useQueryState('foo') - set(null) - set(old => { - expectType(old) - return null - }) -} -{ - const [, set] = useQueryState('foo', queryTypes.integer) - set(null) - set(old => { - expectType(old) - return null - }) -} -{ - const [, set] = useQueryState('foo', queryTypes.float.withDefault(0.2)) - set(null) - set(old => { - expectType(old) // We know it's not null here - return null // But we can return null to clear the query - }) -} - -// Allow specifying just the default value for a string type -{ - useQueryState('foo', { - defaultValue: 'bar' - }) - const [val, set] = useQueryState('foo', { - defaultValue: 'bar', - history: 'push' - }) - expectType(val) - set(null) - set(old => { - expectType(old) // We know it's not null here - return null // But we can return null to clear the query - }) - expectError(() => { - useQueryState('foo', { - defaultValue: 2 // not allowed for other types - }) - }) -} - -// Expect errors on misuse -{ - expectError(() => { - useQueryState('foo', { - parse: (str: string) => str.length, - serialize: value => value.toUpperCase() - }) - }) -} -{ - expectError(() => { - // parser not specified, defaults to string, should clash with explicit hook type - useQueryState('foo') - }) -} - -// Set state to undefined -{ - const [, setFoo] = useQueryState('foo') - const [, setBar] = useQueryState('bar', queryTypes.string.withDefault('egg')) - expectError(() => setFoo(undefined)) - expectError(() => setBar(undefined)) - expectError(() => setFoo(() => undefined)) - expectError(() => setBar(() => undefined)) -} diff --git a/packages/nuqs/src/tests/compat/useQueryStates.test-d.ts b/packages/nuqs/src/tests/compat/useQueryStates.test-d.ts deleted file mode 100644 index eafc14a51..000000000 --- a/packages/nuqs/src/tests/compat/useQueryStates.test-d.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { expectError, expectNotAssignable, expectType } from 'tsd' -import { queryTypes, useQueryStates } from '../../../dist' - -{ - const [states, setStates] = useQueryStates( - { - a: queryTypes.string, - b: queryTypes.integer, - c: queryTypes.float, - d: queryTypes.boolean - }, - { - history: 'push' - } - ) - expectType<{ - a: string | null - b: number | null - c: number | null - d: boolean | null - }>(states) - setStates({ - a: 'foo', - c: 3.14 - }) - setStates(old => ({ - ...old, - d: !old.d - })) -} - -// With default values, state is no longer nullable -{ - const [states, setStates] = useQueryStates({ - hasDefault: queryTypes.string.withDefault('foo'), - doesNot: queryTypes.isoDateTime - }) - expectType<{ - hasDefault: string - doesNot: Date | null - }>(states) - expectNotAssignable(states.hasDefault) - states.doesNot = null - // `null` should always be accepted as setStates - setStates({ - hasDefault: null, - doesNot: null - }) - setStates(() => ({ - hasDefault: null, - doesNot: null - })) - // but not at root level - expectError(() => { - setStates(null) - }) -} - -// Custom parsers -{ - const [states] = useQueryStates({ - hex: { - parse: input => parseInt(input, 16), - serialize: (value: number) => value.toString(16) - }, - bin: { - parse: input => Buffer.from(input), - defaultValue: Buffer.from('') - } - }) - expectType<{ - hex: number | null - bin: Buffer - }>(states) -} diff --git a/packages/nuqs/src/useQueryState.ts b/packages/nuqs/src/useQueryState.ts index 6edfbe3a2..65e0ad4c7 100644 --- a/packages/nuqs/src/useQueryState.ts +++ b/packages/nuqs/src/useQueryState.ts @@ -49,7 +49,7 @@ export type UseQueryStateReturn = [ * ```ts * const [count, setCount] = useQueryState( * 'count', - * queryTypes.integer.defaultValue(0) + * parseAsInteger.defaultValue(0) * ) * * const increment = () => setCount(oldCount => oldCount + 1) @@ -104,11 +104,11 @@ export function useQueryState( * If the query is missing in the URL, the state will be `null`. * * Note: by default the state type is a `string`. To use different types, - * check out the `queryTypes` helpers: + * check out the `parseAsXYZ` helpers: * ```ts * const [date, setDate] = useQueryState( * 'date', - * queryTypes.isoDateTime.withDefault(new Date('2021-01-01')) + * parseAsIsoDateTime.withDefault(new Date('2021-01-01')) * ) * * const setToNow = () => setDate(new Date()) @@ -130,11 +130,11 @@ export function useQueryState( * If the query is missing in the URL, the state will be `null`. * * Note: by default the state type is a `string`. To use different types, - * check out the `queryTypes` helpers: + * check out the `parseAsXYZ` helpers: * ```ts * const [date, setDate] = useQueryState( * 'date', - * queryTypes.isoDateTime.withDefault(new Date('2021-01-01')) + * parseAsIsoDateTime.withDefault(new Date('2021-01-01')) * ) * * const setToNow = () => setDate(new Date()) @@ -173,7 +173,7 @@ export function useQueryState( * * const [count, setCount] = useQueryState( * 'count', - * queryTypes.integer.defaultValue(0) + * parseAsInteger.defaultValue(0) * ) * * const increment = () => setCount(oldCount => oldCount + 1) @@ -184,7 +184,7 @@ export function useQueryState( * * const [date, setDate] = useQueryState( * 'date', - * queryTypes.isoDateTime.withDefault(new Date('2021-01-01')) + * parseAsIsoDateTime.withDefault(new Date('2021-01-01')) * ) * * const setToNow = () => setDate(new Date()) diff --git a/packages/nuqs/src/useQueryStates.ts b/packages/nuqs/src/useQueryStates.ts index 19c480ecb..1c4575157 100644 --- a/packages/nuqs/src/useQueryStates.ts +++ b/packages/nuqs/src/useQueryStates.ts @@ -53,7 +53,7 @@ export type UseQueryStatesReturn = [ * * @param keys - An object describing the keys to synchronise and how to * serialise and parse them. - * Use `queryTypes.(string|integer|float)` for quick shorthands. + * Use `parseAs(String|Integer|Float|...)` for quick shorthands. * @param options - Optional history mode, shallow routing and scroll restoration options. */ export function useQueryStates(