From 84c5189f2eb3e1d005608367e0ddc1a6a758166b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?mart=C3=ADn?= Date: Fri, 15 Nov 2024 10:01:57 -0300 Subject: [PATCH] fix(serializer/types): Allow `null` value in serializer for parsers with default values (#769) Allow passing a `null` value for a key where the parser has a default value, to clear it. Note that it won't write the default value in the output string, it will only clear the key if it was present in the base, like the hooks would. --- packages/nuqs/src/parsers.ts | 10 +++++----- packages/nuqs/src/serializer.test.ts | 14 ++++++++++++++ packages/nuqs/src/serializer.ts | 4 ++-- packages/nuqs/src/tests/serializer.test-d.ts | 13 +++++++++++++ 4 files changed, 34 insertions(+), 7 deletions(-) diff --git a/packages/nuqs/src/parsers.ts b/packages/nuqs/src/parsers.ts index 82973a601..9a94681b2 100644 --- a/packages/nuqs/src/parsers.ts +++ b/packages/nuqs/src/parsers.ts @@ -421,13 +421,13 @@ export function parseAsArrayOf( } type inferSingleParserType = Parser extends ParserBuilder< - infer Type + infer Value > & { - defaultValue: infer Type + defaultValue: infer Value } - ? Type - : Parser extends ParserBuilder - ? Type | null + ? Value + : Parser extends ParserBuilder + ? Value | null : never type inferParserRecordType>> = { diff --git a/packages/nuqs/src/serializer.test.ts b/packages/nuqs/src/serializer.test.ts index e924cc660..5ef06ae53 100644 --- a/packages/nuqs/src/serializer.test.ts +++ b/packages/nuqs/src/serializer.test.ts @@ -86,6 +86,20 @@ describe('serializer', () => { const result = serialize('?str=foo&external=kept', null) expect(result).toBe('?external=kept') }) + test('clears value when setting null for search param that has a default value', () => { + const serialize = createSerializer({ + int: parseAsInteger.withDefault(0) + }) + const result = serialize('?int=1&str=foo', { int: null }) + expect(result).toBe('?str=foo') + }) + test('clears value when setting null for search param that is set to its default value', () => { + const serialize = createSerializer({ + int: parseAsInteger.withDefault(0) + }) + const result = serialize('?int=0&str=foo', { int: null }) + expect(result).toBe('?str=foo') + }) test('clears value when setting the default value (`clearOnDefault: true` is the default)', () => { const serialize = createSerializer({ int: parseAsInteger.withDefault(0), diff --git a/packages/nuqs/src/serializer.ts b/packages/nuqs/src/serializer.ts index 6b9b2a540..272c05b18 100644 --- a/packages/nuqs/src/serializer.ts +++ b/packages/nuqs/src/serializer.ts @@ -1,4 +1,4 @@ -import type { Options } from './defs' +import type { Nullable, Options } from './defs' import type { inferParserType, ParserBuilder } from './parsers' import { renderQueryString } from './url-encoding' @@ -16,7 +16,7 @@ export function createSerializer< urlKeys?: Partial> } = {} ) { - type Values = Partial> + type Values = Partial>> /** * Generate a query string for the given values. diff --git a/packages/nuqs/src/tests/serializer.test-d.ts b/packages/nuqs/src/tests/serializer.test-d.ts index 55a5eaf73..f07eb5215 100644 --- a/packages/nuqs/src/tests/serializer.test-d.ts +++ b/packages/nuqs/src/tests/serializer.test-d.ts @@ -48,3 +48,16 @@ import { createSerializer, parseAsInteger, parseAsString } from '../../dist' serialize({ nope: null }) }) } + +// It accepts null for values +{ + const serialize = createSerializer({ + foo: parseAsInteger, + bar: parseAsInteger.withDefault(0) + }) + // Should accept number | null | undefined + expectType(serialize({ foo: null })) + expectType(serialize({ foo: undefined })) + expectType(serialize({ bar: null })) + expectType(serialize({ bar: undefined })) +}