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

feat: Add UrlKeys type helper #824

Merged
merged 1 commit into from
Dec 26, 2024
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
10 changes: 4 additions & 6 deletions packages/nuqs/src/cache.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
// @ts-ignore
import { cache } from 'react'
import type { SearchParams } from './defs'
import type { SearchParams, UrlKeys } from './defs'
import { error } from './errors'
import type { ParserBuilder, inferParserType } from './parsers'
import type { inferParserType, ParserMap } from './parsers'

const $input: unique symbol = Symbol('Input')

export function createSearchParamsCache<
Parsers extends Record<string, ParserBuilder<any>>
>(
export function createSearchParamsCache<Parsers extends ParserMap>(
parsers: Parsers,
{ urlKeys = {} }: { urlKeys?: Partial<Record<keyof Parsers, string>> } = {}
{ urlKeys = {} }: { urlKeys?: UrlKeys<Parsers> } = {}
) {
type Keys = keyof Parsers
type ParsedSearchParams = {
Expand Down
32 changes: 32 additions & 0 deletions packages/nuqs/src/defs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,35 @@ export type Options = {
export type Nullable<T> = {
[K in keyof T]: T[K] | null
}

/**
* Helper type to define and reuse urlKey options to rename search params keys
*
* Usage:
* ```ts
* import { type UrlKeys } from 'nuqs' // or 'nuqs/server'
*
* export const coordinatesSearchParams = {
* latitude: parseAsFloat.withDefault(0),
* longitude: parseAsFloat.withDefault(0),
* }
* export const coordinatesUrlKeys: UrlKeys<typeof coordinatesSearchParams> = {
* latitude: 'lat',
* longitude: 'lng',
* }
*
* // Later in the code:
* useQueryStates(coordinatesSearchParams, {
* urlKeys: coordinatesUrlKeys
* })
* createSerializer(coordinatesSearchParams, {
* urlKeys: coordinatesUrlKeys
* })
* createSearchParamsCache(coordinatesSearchParams, {
* urlKeys: coordinatesUrlKeys
* })
* ```
*/
export type UrlKeys<Parsers extends Record<string, any>> = Partial<
Record<keyof Parsers, string>
>
5 changes: 5 additions & 0 deletions packages/nuqs/src/parsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -464,3 +464,8 @@ export type inferParserType<Input> =
: Input extends Record<string, ParserBuilder<any>>
? inferParserRecordType<Input>
: never

export type ParserWithOptionalDefault<T> = ParserBuilder<T> & {
defaultValue?: T
}
export type ParserMap = Record<string, ParserWithOptionalDefault<any>>
11 changes: 4 additions & 7 deletions packages/nuqs/src/serializer.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
import type { Nullable, Options } from './defs'
import type { inferParserType, ParserBuilder } from './parsers'
import type { Nullable, Options, UrlKeys } from './defs'
import type { inferParserType, ParserMap } from './parsers'
import { renderQueryString } from './url-encoding'

type Base = string | URLSearchParams | URL
type ParserWithOptionalDefault<T> = ParserBuilder<T> & { defaultValue?: T }

export function createSerializer<
Parsers extends Record<string, ParserWithOptionalDefault<any>>
>(
export function createSerializer<Parsers extends ParserMap>(
parsers: Parsers,
{
clearOnDefault = true,
urlKeys = {}
}: Pick<Options, 'clearOnDefault'> & {
urlKeys?: Partial<Record<keyof Parsers, string>>
urlKeys?: UrlKeys<Parsers>
} = {}
) {
type Values = Partial<Nullable<inferParserType<Parsers>>>
Expand Down
41 changes: 41 additions & 0 deletions packages/nuqs/src/tests/cache.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,44 @@ import {
expectType<Promise<All>>(cache.parse(Promise.resolve({})))
expectType<All>(cache.all())
}

// It supports urlKeys
{
createSearchParamsCache(
{
foo: parseAsString,
bar: parseAsInteger
},
{
urlKeys: {
foo: 'f'
// It accepts partial inputs
}
}
)
createSearchParamsCache(
{
foo: parseAsString,
bar: parseAsInteger
},
{
urlKeys: {
foo: 'f',
bar: 'b'
}
}
)
expectError(() => {
createSearchParamsCache(
{
foo: parseAsString,
bar: parseAsInteger
},
{
urlKeys: {
nope: 'n' // Doesn't accept extra properties
}
}
)
})
}
41 changes: 41 additions & 0 deletions packages/nuqs/src/tests/serializer.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,44 @@ import { createSerializer, parseAsInteger, parseAsString } from '../../dist'
expectType<string>(serialize({ bar: null }))
expectType<string>(serialize({ bar: undefined }))
}

// It supports urlKeys
{
createSerializer(
{
foo: parseAsString,
bar: parseAsInteger
},
{
urlKeys: {
foo: 'f'
// It accepts partial inputs
}
}
)
createSerializer(
{
foo: parseAsString,
bar: parseAsInteger
},
{
urlKeys: {
foo: 'f',
bar: 'b'
}
}
)
expectError(() => {
createSerializer(
{
foo: parseAsString,
bar: parseAsInteger
},
{
urlKeys: {
nope: 'n' // Doesn't accept extra properties
}
}
)
})
}
6 changes: 3 additions & 3 deletions packages/nuqs/src/useQueryStates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import {
} from 'react'
import { useAdapter } from './adapters/lib/context'
import { debug } from './debug'
import type { Nullable, Options } from './defs'
import type { Nullable, Options, UrlKeys } from './defs'
import type { Parser } from './parsers'
import { emitter, type CrossHookSyncPayload } from './sync'
import {
FLUSH_RATE_LIMIT_MS,
enqueueQueryStringUpdate,
FLUSH_RATE_LIMIT_MS,
getQueuedValue,
scheduleFlushToURL
} from './update-queue'
Expand All @@ -30,7 +30,7 @@ export type UseQueryStatesKeysMap<Map = any> = {

export type UseQueryStatesOptions<KeyMap extends UseQueryStatesKeysMap> =
Options & {
urlKeys: Partial<Record<keyof KeyMap, string>>
urlKeys: UrlKeys<KeyMap>
}

export type Values<T extends UseQueryStatesKeysMap> = {
Expand Down
Loading