diff --git a/packages/adapters/react/src/components/search-input.tsx b/packages/adapters/react/src/components/search-input.tsx
index b6baf42cc..438e8a7bb 100644
--- a/packages/adapters/react/src/components/search-input.tsx
+++ b/packages/adapters/react/src/components/search-input.tsx
@@ -2,7 +2,7 @@ import { parseAsString, useQueryStates } from 'nuqs'
export function SearchInput() {
const [{ search }, setSearch] = useQueryStates({
- search: parseAsString.withDefault('').withOptions({ clearOnDefault: true })
+ search: parseAsString.withDefault('')
})
return (
({
function useGameEngine() {
const [{ board, status }, setGameState] = useQueryState(
'board',
- gameParser
- .withDefault(defaultState)
- .withOptions({ clearOnDefault: true, history: 'push' })
+ gameParser.withDefault(defaultState).withOptions({ history: 'push' })
)
const play = useCallback(
(i: number, j: number) => {
diff --git a/packages/e2e/cypress/e2e/clearOnDefault.cy.js b/packages/e2e/cypress/e2e/clearOnDefault.cy.js
index 42097ef23..942322b8f 100644
--- a/packages/e2e/cypress/e2e/clearOnDefault.cy.js
+++ b/packages/e2e/cypress/e2e/clearOnDefault.cy.js
@@ -2,9 +2,9 @@
it('Clears the URL when setting the default value when `clearOnDefault` is used', () => {
cy.visit(
- '/app/clearOnDefault?a=a&b=b&array=1,2,3&json-ref={"egg":"spam"}&json-new={"egg":"spam"}'
+ '/app/clearOnDefault?a=a&b=b&array=1,2,3&json-ref={"egg":"spam"}&json-new={"egg":"spam"}&keepMe=init'
)
cy.contains('#hydration-marker', 'hydrated').should('be.hidden')
cy.get('button').click()
- cy.location('search').should('eq', '?a=')
+ cy.location('search').should('eq', '?a=&keepMe=')
})
diff --git a/packages/e2e/src/app/app/clearOnDefault/page.tsx b/packages/e2e/src/app/app/clearOnDefault/page.tsx
index 3a268e02a..42f5df888 100644
--- a/packages/e2e/src/app/app/clearOnDefault/page.tsx
+++ b/packages/e2e/src/app/app/clearOnDefault/page.tsx
@@ -4,6 +4,7 @@ import {
parseAsArrayOf,
parseAsInteger,
parseAsJson,
+ parseAsString,
useQueryState
} from 'nuqs'
import { Suspense } from 'react'
@@ -22,26 +23,23 @@ const runtimePassthrough = (x: unknown) => x
function Client() {
const [, setA] = useQueryState('a')
const [, setB] = useQueryState('b', {
- defaultValue: '',
- clearOnDefault: true
+ defaultValue: ''
})
const [, setArray] = useQueryState(
'array',
- parseAsArrayOf(parseAsInteger)
- .withDefault([])
- .withOptions({ clearOnDefault: true })
+ parseAsArrayOf(parseAsInteger).withDefault([])
)
const [, setJsonRef] = useQueryState(
'json-ref',
- parseAsJson(runtimePassthrough)
- .withDefault(defaultJSON)
- .withOptions({ clearOnDefault: true })
+ parseAsJson(runtimePassthrough).withDefault(defaultJSON)
)
const [, setJsonNew] = useQueryState(
'json-new',
- parseAsJson(runtimePassthrough)
- .withDefault(defaultJSON)
- .withOptions({ clearOnDefault: true })
+ parseAsJson(runtimePassthrough).withDefault(defaultJSON)
+ )
+ const [, keepMe] = useQueryState(
+ 'keepMe',
+ parseAsString.withDefault('').withOptions({ clearOnDefault: false })
)
return (
<>
@@ -52,6 +50,7 @@ function Client() {
setArray([])
setJsonRef(defaultJSON)
setJsonNew({ ...defaultJSON })
+ keepMe('')
}}
>
Clear
diff --git a/packages/e2e/src/app/app/remapped-keys/page.tsx b/packages/e2e/src/app/app/remapped-keys/page.tsx
index b14c5082f..27b67c6ab 100644
--- a/packages/e2e/src/app/app/remapped-keys/page.tsx
+++ b/packages/e2e/src/app/app/remapped-keys/page.tsx
@@ -24,7 +24,6 @@ function Client() {
activeTags: parseAsArrayOf(parseAsString).withDefault([])
},
{
- clearOnDefault: true,
urlKeys: {
searchQuery: 'q',
pageNumber: 'page',
diff --git a/packages/e2e/src/app/app/template/page.tsx b/packages/e2e/src/app/app/template/page.tsx
index d980703bd..23609b34f 100644
--- a/packages/e2e/src/app/app/template/page.tsx
+++ b/packages/e2e/src/app/app/template/page.tsx
@@ -13,8 +13,7 @@ export default function Page() {
function Client() {
const [state, setState] = useQueryState('state', {
- defaultValue: '',
- clearOnDefault: true
+ defaultValue: ''
})
return (
<>
diff --git a/packages/nuqs/src/defs.ts b/packages/nuqs/src/defs.ts
index 1afdf32ea..49fe764b2 100644
--- a/packages/nuqs/src/defs.ts
+++ b/packages/nuqs/src/defs.ts
@@ -54,7 +54,9 @@ export type Options = {
* Clear the key-value pair from the URL query string when setting the state
* to the default value.
*
- * Defaults to `false` to keep backwards-compatiblity when the default value
+ * Defaults to `true` to keep URLs clean.
+ *
+ * Set it to `false` to keep backwards-compatiblity when the default value
* changes (prefer explicit URLs whose meaning don't change).
*/
clearOnDefault?: boolean
diff --git a/packages/nuqs/src/serializer.test.ts b/packages/nuqs/src/serializer.test.ts
index 561c183f5..b147af6e7 100644
--- a/packages/nuqs/src/serializer.test.ts
+++ b/packages/nuqs/src/serializer.test.ts
@@ -1,4 +1,5 @@
import { describe, expect, test } from 'vitest'
+import type { Options } from './defs'
import {
parseAsArrayOf,
parseAsBoolean,
@@ -85,18 +86,32 @@ describe('serializer', () => {
const result = serialize('?str=foo&external=kept', null)
expect(result).toBe('?external=kept')
})
- test('clears value when setting the default value when `clearOnDefault` is used', () => {
+ test('clears value when setting the default value (`clearOnDefault: true` is the default)', () => {
const serialize = createSerializer({
- int: parseAsInteger.withOptions({ clearOnDefault: true }).withDefault(0),
- str: parseAsString.withOptions({ clearOnDefault: true }).withDefault(''),
- bool: parseAsBoolean
- .withOptions({ clearOnDefault: true })
- .withDefault(false),
- arr: parseAsArrayOf(parseAsString)
- .withOptions({ clearOnDefault: true })
- .withDefault([]),
+ int: parseAsInteger.withDefault(0),
+ str: parseAsString.withDefault(''),
+ bool: parseAsBoolean.withDefault(false),
+ arr: parseAsArrayOf(parseAsString).withDefault([]),
+ json: parseAsJson(x => x).withDefault({ foo: 'bar' })
+ })
+ const result = serialize({
+ int: 0,
+ str: '',
+ bool: false,
+ arr: [],
+ json: { foo: 'bar' }
+ })
+ expect(result).toBe('')
+ })
+ test('keeps value when setting the default value when `clearOnDefault: false`', () => {
+ const options: Options = { clearOnDefault: false }
+ const serialize = createSerializer({
+ int: parseAsInteger.withOptions(options).withDefault(0),
+ str: parseAsString.withOptions(options).withDefault(''),
+ bool: parseAsBoolean.withOptions(options).withDefault(false),
+ arr: parseAsArrayOf(parseAsString).withOptions(options).withDefault([]),
json: parseAsJson(x => x)
- .withOptions({ clearOnDefault: true })
+ .withOptions(options)
.withDefault({ foo: 'bar' })
})
const result = serialize({
@@ -106,6 +121,8 @@ describe('serializer', () => {
arr: [],
json: { foo: 'bar' }
})
- expect(result).toBe('')
+ expect(result).toBe(
+ '?int=0&str=&bool=false&arr=&json={%22foo%22:%22bar%22}'
+ )
})
})
diff --git a/packages/nuqs/src/serializer.ts b/packages/nuqs/src/serializer.ts
index 7e3f147d3..a97c3cb5f 100644
--- a/packages/nuqs/src/serializer.ts
+++ b/packages/nuqs/src/serializer.ts
@@ -52,7 +52,10 @@ export function createSerializer<
parser.defaultValue !== undefined &&
(parser.eq ?? ((a, b) => a === b))(value, parser.defaultValue)
- if (value === null || (parser.clearOnDefault && isMatchingDefault)) {
+ if (
+ value === null ||
+ ((parser.clearOnDefault ?? true) && isMatchingDefault)
+ ) {
search.delete(key)
} else {
search.set(key, parser.serialize(value))
diff --git a/packages/nuqs/src/useQueryState.ts b/packages/nuqs/src/useQueryState.ts
index 42b5f73be..58febcd26 100644
--- a/packages/nuqs/src/useQueryState.ts
+++ b/packages/nuqs/src/useQueryState.ts
@@ -211,7 +211,7 @@ export function useQueryState(
serialize = String,
eq = (a, b) => a === b,
defaultValue = undefined,
- clearOnDefault = false,
+ clearOnDefault = true,
startTransition
}: Partial> & {
defaultValue?: T
@@ -223,7 +223,7 @@ export function useQueryState(
parse: x => x as unknown as T,
serialize: String,
eq: (a, b) => a === b,
- clearOnDefault: false,
+ clearOnDefault: true,
defaultValue: undefined
}
) {
diff --git a/packages/nuqs/src/useQueryStates.ts b/packages/nuqs/src/useQueryStates.ts
index 2e1fc6dff..0b640a351 100644
--- a/packages/nuqs/src/useQueryStates.ts
+++ b/packages/nuqs/src/useQueryStates.ts
@@ -74,7 +74,7 @@ export function useQueryStates(
scroll = false,
shallow = true,
throttleMs = FLUSH_RATE_LIMIT_MS,
- clearOnDefault = false,
+ clearOnDefault = true,
startTransition,
urlKeys = defaultUrlKeys
}: Partial> = {}