From dc5667445558eb12fbe4645469db0c8702e57fe7 Mon Sep 17 00:00:00 2001 From: Tim Heerwagen Date: Thu, 11 Jan 2024 09:59:19 +0100 Subject: [PATCH 1/4] feat: Add parseAsStringConst --- README.md | 14 ++++++++-- packages/docs/content/docs/index.mdx | 12 +++++++- packages/nuqs/src/parsers.ts | 41 +++++++++++++++++++++++++--- 3 files changed, 60 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 4c915047..54146676 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,8 @@ import { parseAsIsoDateTime, parseAsArrayOf, parseAsJson, - parseAsStringEnum + parseAsStringEnum, + parseAsStringConst } from 'nuqs' useQueryState('tag') // defaults to string @@ -128,6 +129,15 @@ const [direction, setDirection] = useQueryState( parseAsStringEnum(Object.values(Direction)) // pass a list of allowed values .withDefault(Direction.up) ) + +// readonly Array (string-based only) +const colors = ['red', 'green', 'blue'] as const + +const [color, setColor] = useQueryState( + 'color', + parseAsStringConst(colors) // pass a readonly list of allowed values + .withDefault('red') +) ``` You may pass a custom set of `parse` and `serialize` functions: @@ -594,7 +604,7 @@ export function Server() { // client.tsx // prettier-ignore -;'use client' +'use client' import { useQueryStates } from 'nuqs' import { coordinatesParsers } from './searchParams' diff --git a/packages/docs/content/docs/index.mdx b/packages/docs/content/docs/index.mdx index c944f9e5..edeb3a38 100644 --- a/packages/docs/content/docs/index.mdx +++ b/packages/docs/content/docs/index.mdx @@ -94,7 +94,8 @@ import { parseAsIsoDateTime, parseAsArrayOf, parseAsJson, - parseAsStringEnum + parseAsStringEnum, + parseAsStringConst } from 'nuqs' useQueryState('tag') // defaults to string @@ -119,6 +120,15 @@ const [direction, setDirection] = useQueryState( parseAsStringEnum(Object.values(Direction)) // pass a list of allowed values .withDefault(Direction.up) ) + +// readonly Array (string-based only) +const colors = ['red', 'green', 'blue'] as const + +const [color, setColor] = useQueryState( + 'color', + parseAsStringConst(colors) // pass a readonly list of allowed values + .withDefault('red') +) ``` You may pass a custom set of `parse` and `serialize` functions: diff --git a/packages/nuqs/src/parsers.ts b/packages/nuqs/src/parsers.ts index 2d3a1c4c..e0897481 100644 --- a/packages/nuqs/src/parsers.ts +++ b/packages/nuqs/src/parsers.ts @@ -191,7 +191,7 @@ export const parseAsIsoDateTime = createParser({ /** * String-based enums provide better type-safety for known sets of values. - * You will need to pass the stringEnum function a list of your enum values + * You will need to pass the parseAsStringEnum function a list of your enum values * in order to validate the query string. Anything else will return `null`, * or your default value if specified. * @@ -206,9 +206,8 @@ export const parseAsIsoDateTime = createParser({ * * const [direction, setDirection] = useQueryState( * 'direction', - * queryTypes - * .stringEnum(Object.values(Direction)) - * .withDefault(Direction.up) + * parseAsStringEnum(Object.values(Direction)) // pass a list of allowed values + * .withDefault(Direction.up) * ) * ``` * @@ -230,6 +229,40 @@ export function parseAsStringEnum(validValues: Enum[]) { }) } +/** + * String-based readonly lists provide better type-safety for known sets of values. + * You will need to pass the parseAsStringConst function a list of your string values + * in order to validate the query string. Anything else will return `null`, + * or your default value if specified. + * + * Example: + * ```ts + * const colors = ["red", "green", "blue"] as const + * + * const [color, setColor] = useQueryState( + * 'color', + * parseAsStringConst(colors) // pass a readonly list of allowed values + * .withDefault("red") + * ) + * ``` + * + * @param validValues The values you want to accept + */ +export function parseAsStringConst( + validValues: readonly Const[] +) { + return createParser({ + parse: (query: string) => { + const asConst = query as unknown as Const + if (validValues.includes(asConst)) { + return asConst + } + return null + }, + serialize: (value: Const) => value.toString() + }) +} + /** * Encode any object shape into the querystring value as JSON. * Value is URI-encoded for safety, so it may not look nice in the URL. From afb16ec3f8c5e9c8b7813181e11ac17aacd702ff Mon Sep 17 00:00:00 2001 From: Tim Heerwagen Date: Thu, 11 Jan 2024 22:08:28 +0100 Subject: [PATCH 2/4] feat: Add parseAsLiteral --- README.md | 6 +++--- packages/docs/content/docs/index.mdx | 6 +++--- packages/nuqs/src/parsers.ts | 14 +++++++------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 54146676..eb71f617 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ import { parseAsArrayOf, parseAsJson, parseAsStringEnum, - parseAsStringConst + parseAsLiteral } from 'nuqs' useQueryState('tag') // defaults to string @@ -130,12 +130,12 @@ const [direction, setDirection] = useQueryState( .withDefault(Direction.up) ) -// readonly Array (string-based only) +// Literals (string- or number-based only) const colors = ['red', 'green', 'blue'] as const const [color, setColor] = useQueryState( 'color', - parseAsStringConst(colors) // pass a readonly list of allowed values + parseAsLiteral(colors) // pass a readonly list of allowed values .withDefault('red') ) ``` diff --git a/packages/docs/content/docs/index.mdx b/packages/docs/content/docs/index.mdx index edeb3a38..b54a1e35 100644 --- a/packages/docs/content/docs/index.mdx +++ b/packages/docs/content/docs/index.mdx @@ -95,7 +95,7 @@ import { parseAsArrayOf, parseAsJson, parseAsStringEnum, - parseAsStringConst + parseAsLiteral } from 'nuqs' useQueryState('tag') // defaults to string @@ -121,12 +121,12 @@ const [direction, setDirection] = useQueryState( .withDefault(Direction.up) ) -// readonly Array (string-based only) +// Literals (string- or number-based only) const colors = ['red', 'green', 'blue'] as const const [color, setColor] = useQueryState( 'color', - parseAsStringConst(colors) // pass a readonly list of allowed values + parseAsLiteral(colors) // pass a readonly list of allowed values .withDefault('red') ) ``` diff --git a/packages/nuqs/src/parsers.ts b/packages/nuqs/src/parsers.ts index e0897481..533800e0 100644 --- a/packages/nuqs/src/parsers.ts +++ b/packages/nuqs/src/parsers.ts @@ -230,8 +230,8 @@ export function parseAsStringEnum(validValues: Enum[]) { } /** - * String-based readonly lists provide better type-safety for known sets of values. - * You will need to pass the parseAsStringConst function a list of your string values + * String- or number-based readonly lists provide better type-safety for known sets of values. + * You will need to pass the parseAsLiteral function a list of your string or number values * in order to validate the query string. Anything else will return `null`, * or your default value if specified. * @@ -241,25 +241,25 @@ export function parseAsStringEnum(validValues: Enum[]) { * * const [color, setColor] = useQueryState( * 'color', - * parseAsStringConst(colors) // pass a readonly list of allowed values + * parseAsLiteral(colors) // pass a readonly list of allowed values * .withDefault("red") * ) * ``` * * @param validValues The values you want to accept */ -export function parseAsStringConst( - validValues: readonly Const[] +export function parseAsLiteral( + validValues: readonly Literal[] ) { return createParser({ parse: (query: string) => { - const asConst = query as unknown as Const + const asConst = query as unknown as Literal if (validValues.includes(asConst)) { return asConst } return null }, - serialize: (value: Const) => value.toString() + serialize: (value: Literal) => value.toString() }) } From 0a440b79e5cc8c01ca521c35f508e7ed5c6d334c Mon Sep 17 00:00:00 2001 From: Tim Heerwagen Date: Fri, 12 Jan 2024 13:16:19 +0100 Subject: [PATCH 3/4] feat: Split parseAsLiteral into parseAsStringLiteral and parseAsNumberLiteral --- README.md | 16 +++++++++-- packages/docs/content/docs/index.mdx | 16 +++++++++-- packages/nuqs/src/parsers.ts | 42 +++++++++++++++++++++++++--- 3 files changed, 64 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index eb71f617..1386995b 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,8 @@ import { parseAsArrayOf, parseAsJson, parseAsStringEnum, - parseAsLiteral + parseAsStringLiteral, + parseAsNumberLiteral } from 'nuqs' useQueryState('tag') // defaults to string @@ -130,12 +131,21 @@ const [direction, setDirection] = useQueryState( .withDefault(Direction.up) ) -// Literals (string- or number-based only) +// Literals (string-based only) const colors = ['red', 'green', 'blue'] as const const [color, setColor] = useQueryState( 'color', - parseAsLiteral(colors) // pass a readonly list of allowed values + parseAsStringLiteral(colors) // pass a readonly list of allowed values + .withDefault('red') +) + +// Literals (number-based only) +const diceSides = [1, 2, 3, 4, 5, 6] as const + +const [side, setSide] = useQueryState( + 'side', + parseAsNumberLiteral(diceSides) // pass a readonly list of allowed values .withDefault('red') ) ``` diff --git a/packages/docs/content/docs/index.mdx b/packages/docs/content/docs/index.mdx index b54a1e35..db1ce12b 100644 --- a/packages/docs/content/docs/index.mdx +++ b/packages/docs/content/docs/index.mdx @@ -95,7 +95,8 @@ import { parseAsArrayOf, parseAsJson, parseAsStringEnum, - parseAsLiteral + parseAsStringLiteral, + parseAsNumberLiteral } from 'nuqs' useQueryState('tag') // defaults to string @@ -121,12 +122,21 @@ const [direction, setDirection] = useQueryState( .withDefault(Direction.up) ) -// Literals (string- or number-based only) +// Literals (string-based only) const colors = ['red', 'green', 'blue'] as const const [color, setColor] = useQueryState( 'color', - parseAsLiteral(colors) // pass a readonly list of allowed values + parseAsStringLiteral(colors) // pass a readonly list of allowed values + .withDefault('red') +) + +// Literals (number-based only) +const diceSides = [1, 2, 3, 4, 5, 6] as const + +const [side, setSide] = useQueryState( + 'side', + parseAsNumberLiteral(diceSides) // pass a readonly list of allowed values .withDefault('red') ) ``` diff --git a/packages/nuqs/src/parsers.ts b/packages/nuqs/src/parsers.ts index 533800e0..e614fd54 100644 --- a/packages/nuqs/src/parsers.ts +++ b/packages/nuqs/src/parsers.ts @@ -230,8 +230,8 @@ export function parseAsStringEnum(validValues: Enum[]) { } /** - * String- or number-based readonly lists provide better type-safety for known sets of values. - * You will need to pass the parseAsLiteral function a list of your string or number values + * String-based literals provide better type-safety for known sets of values. + * You will need to pass the parseAsStringLiteral function a list of your string values * in order to validate the query string. Anything else will return `null`, * or your default value if specified. * @@ -241,14 +241,14 @@ export function parseAsStringEnum(validValues: Enum[]) { * * const [color, setColor] = useQueryState( * 'color', - * parseAsLiteral(colors) // pass a readonly list of allowed values + * parseAsStringLiteral(colors) // pass a readonly list of allowed values * .withDefault("red") * ) * ``` * * @param validValues The values you want to accept */ -export function parseAsLiteral( +export function parseAsStringLiteral( validValues: readonly Literal[] ) { return createParser({ @@ -263,6 +263,40 @@ export function parseAsLiteral( }) } +/** + * Number-based literals provide better type-safety for known sets of values. + * You will need to pass the parseAsNumberLiteral function a list of your number values + * in order to validate the query string. Anything else will return `null`, + * or your default value if specified. + * + * Example: + * ```ts + * const diceSides = [1, 2, 3, 4, 5, 6] as const + * + * const [side, setSide] = useQueryState( + * 'side', + * parseAsNumberLiteral(diceSides) // pass a readonly list of allowed values + * .withDefault("red") + * ) + * ``` + * + * @param validValues The values you want to accept + */ +export function parseAsNumberLiteral( + validValues: readonly Literal[] +) { + return createParser({ + parse: (query: string) => { + const asConst = parseFloat(query) as unknown as Literal + if (validValues.includes(asConst)) { + return asConst + } + return null + }, + serialize: (value: Literal) => value.toString() + }) +} + /** * Encode any object shape into the querystring value as JSON. * Value is URI-encoded for safety, so it may not look nice in the URL. From 72377331976750b4a77aa8af68ac46505fb10e5d Mon Sep 17 00:00:00 2001 From: Tim Heerwagen Date: Fri, 12 Jan 2024 13:30:05 +0100 Subject: [PATCH 4/4] doc: Update parseAsNumberLiteral default value --- README.md | 2 +- packages/docs/content/docs/index.mdx | 2 +- packages/nuqs/src/parsers.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1386995b..f7bfb081 100644 --- a/README.md +++ b/README.md @@ -146,7 +146,7 @@ const diceSides = [1, 2, 3, 4, 5, 6] as const const [side, setSide] = useQueryState( 'side', parseAsNumberLiteral(diceSides) // pass a readonly list of allowed values - .withDefault('red') + .withDefault(4) ) ``` diff --git a/packages/docs/content/docs/index.mdx b/packages/docs/content/docs/index.mdx index db1ce12b..be9c2ec9 100644 --- a/packages/docs/content/docs/index.mdx +++ b/packages/docs/content/docs/index.mdx @@ -137,7 +137,7 @@ const diceSides = [1, 2, 3, 4, 5, 6] as const const [side, setSide] = useQueryState( 'side', parseAsNumberLiteral(diceSides) // pass a readonly list of allowed values - .withDefault('red') + .withDefault(4) ) ``` diff --git a/packages/nuqs/src/parsers.ts b/packages/nuqs/src/parsers.ts index e614fd54..98b68dae 100644 --- a/packages/nuqs/src/parsers.ts +++ b/packages/nuqs/src/parsers.ts @@ -276,7 +276,7 @@ export function parseAsStringLiteral( * const [side, setSide] = useQueryState( * 'side', * parseAsNumberLiteral(diceSides) // pass a readonly list of allowed values - * .withDefault("red") + * .withDefault(4) * ) * ``` *