Type-safe search params state manager for Next.js - Like React.useState, but stored in the URL query string
Overall
score
96%
Core hooks for managing URL query parameters as React state, providing type-safe synchronization between component state and URL query strings.
Manages a single query parameter as React state with type safety and optional default values.
/**
* React state hook synchronized with a URL query string in Next.js
* @param key - The URL query string key to bind to
* @param options - Parser (defines the state data type), default value and optional history mode
*/
function useQueryState<T>(
key: string,
options: UseQueryStateOptions<T> & { defaultValue: T }
): UseQueryStateReturn<NonNullable<ReturnType<typeof options.parse>>, typeof options.defaultValue>;
function useQueryState<T>(
key: string,
options: UseQueryStateOptions<T>
): UseQueryStateReturn<NonNullable<ReturnType<typeof options.parse>>, undefined>;
function useQueryState(
key: string,
options: Options & { defaultValue: string }
): UseQueryStateReturn<string, typeof options.defaultValue>;
function useQueryState(
key: string,
options?: Pick<UseQueryStateOptions<string>, keyof Options>
): UseQueryStateReturn<string, undefined>;
function useQueryState(key: string): UseQueryStateReturn<string, undefined>;
interface UseQueryStateOptions<T> extends Parser<T>, Options {}
type UseQueryStateReturn<Parsed, Default> = [
Default extends undefined ? Parsed | null : Parsed,
<Shallow>(
value: Parsed | null | ((old: Default extends Parsed ? Parsed : Parsed | null) => Parsed | null),
options?: Options<Shallow>
) => Promise<URLSearchParams>
];Usage Examples:
import { useQueryState, parseAsInteger, parseAsBoolean } from "nuqs";
// String query parameter (default type)
const [search, setSearch] = useQueryState('q');
// search is string | null, setSearch accepts string | null
// Integer with default value
const [page, setPage] = useQueryState('page', parseAsInteger.withDefault(1));
// page is number, setPage accepts number | null
// Boolean with parser options
const [active, setActive] = useQueryState('active', {
...parseAsBoolean,
history: 'push'
});
// Functional updates
setPage(oldPage => oldPage + 1);
setSearch(null); // Clears the query parameterManages multiple related query parameters as a single state object with atomic updates.
/**
* Synchronise multiple query string arguments to React state in Next.js
* @param keyMap - An object describing the keys to synchronise and how to serialise and parse them
* @param options - Optional history mode, shallow routing and scroll restoration options
*/
function useQueryStates<KeyMap extends UseQueryStatesKeysMap>(
keyMap: KeyMap,
options?: UseQueryStatesOptions & { urlKeys?: Partial<Record<keyof KeyMap, string>> }
): UseQueryStatesReturn<KeyMap>;
type UseQueryStatesKeysMap<Map = any> = {
[Key in keyof Map]: KeyMapValue<Map[Key]>;
};
type KeyMapValue<Type> = Parser<Type> & Options & { defaultValue?: Type };
interface UseQueryStatesOptions extends Options {}
type Values<T extends UseQueryStatesKeysMap> = {
[K in keyof T]: T[K]['defaultValue'] extends NonNullable<ReturnType<T[K]['parse']>>
? NonNullable<ReturnType<T[K]['parse']>>
: ReturnType<T[K]['parse']> | null;
};
type SetValues<T extends UseQueryStatesKeysMap> = (
values: Partial<Nullable<Values<T>>> | UpdaterFn<T> | null,
options?: Options
) => Promise<URLSearchParams>;
type UpdaterFn<T extends UseQueryStatesKeysMap> = (old: Values<T>) => Partial<Nullable<Values<T>>>;
type UseQueryStatesReturn<T extends UseQueryStatesKeysMap> = [Values<T>, SetValues<T>];
type Nullable<T> = { [K in keyof T]: T[K] | null };Usage Examples:
import { useQueryStates, parseAsInteger, parseAsBoolean, parseAsString } from "nuqs";
// Define the query state schema
const [filters, setFilters] = useQueryStates({
search: parseAsString,
page: parseAsInteger.withDefault(1),
active: parseAsBoolean.withDefault(true),
category: parseAsString
});
// Access individual values
console.log(filters.search); // string | null
console.log(filters.page); // number (always defined due to default)
console.log(filters.active); // boolean (always defined due to default)
// Update multiple values atomically
setFilters({
search: 'react',
page: 1,
category: 'frameworks'
});
// Functional updates with access to current state
setFilters(current => ({
page: current.page + 1,
search: current.search?.toUpperCase()
}));
// Clear all managed query parameters
setFilters(null);
// URL key mapping (use different URL keys than state keys)
const [state, setState] = useQueryStates({
searchTerm: parseAsString,
pageNumber: parseAsInteger.withDefault(1)
}, {
urlKeys: {
searchTerm: 'q',
pageNumber: 'p'
}
});Both hooks support comprehensive configuration options:
interface Options<Shallow = unknown> {
/** How the query update affects page history (defaults to 'replace') */
history?: HistoryOptions;
/** Scroll to top after a query state update (defaults to false) */
scroll?: boolean;
/** Shallow mode keeps query states update client-side only (defaults to true) */
shallow?: Extract<Shallow | boolean, boolean>;
/** Maximum time (ms) between URL query string updates (defaults to 50ms) */
throttleMs?: number;
/** Clear key-value pair from URL when setting state to default value */
clearOnDefault?: boolean;
/** React transition function for observing Server Component loading states */
startTransition?: TransitionStartFunction;
}
type HistoryOptions = 'replace' | 'push';
type TransitionStartFunction = (callback: () => void) => void;Configuration Examples:
// Hook-level options
const [search, setSearch] = useQueryState('q', {
history: 'push', // Use browser back/forward
scroll: false, // Don't scroll on updates
throttleMs: 100, // Throttle URL updates
clearOnDefault: true // Remove from URL when set to default
});
// Call-level options (override hook options)
setSearch('react', {
history: 'replace',
shallow: false
});
// React 18 transitions for server updates
const [isPending, startTransition] = useTransition();
const [data, setData] = useQueryState('data', {
shallow: false,
startTransition
});The hooks handle various error conditions gracefully:
nullnull instead of throwingInstall with Tessl CLI
npx tessl i tessl/npm-nuqsevals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10