CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-nuqs

Type-safe search params state manager for Next.js - Like React.useState, but stored in the URL query string

Overall
score

96%

Overview
Eval results
Files

query-states.mddocs/

Query State Management

Core hooks for managing URL query parameters as React state, providing type-safe synchronization between component state and URL query strings.

Capabilities

useQueryState

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 parameter

useQueryStates

Manages 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'
  }
});

Options Configuration

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
});

Error Handling

The hooks handle various error conditions gracefully:

  • Invalid query string values are parsed as null
  • Parser errors return null instead of throwing
  • URL updates are throttled to prevent browser rate limiting
  • Multiple hook instances are automatically synchronized

Install with Tessl CLI

npx tessl i tessl/npm-nuqs

docs

index.md

parsers.md

query-states.md

serialization.md

server-cache.md

tile.json