useUrlState

Two-way URL synchronization for component state.

Use useUrlState to keep component state synchronized with a single URL parameter. This is ideal for inputs, toggles, tabs, pagination, and other value controls.

useUrlState is route-agnostic: it reads the current location search params through TanStack Router and validates only the parameter it owns through its codec.

Usage

import { useUrlState } from "@tilt-legal/cubitt-components/utilities/hooks";

export function SearchInput() {
  const { value, setValue } = useUrlState({
    paramName: "search",
    paramDefaultValue: "",
  });

  return (
    <input
      value={value ?? ""}
      onChange={(event) => void setValue(event.target.value)}
    />
  );
}

Router registration

For the best TypeScript experience, make sure your app has TanStack Router registration in place. With that, paramName narrows to known search keys from your route tree instead of falling back to plain string.

TanStack Start file-based apps usually get this automatically from generated route types. Plain TanStack Router apps should use the standard Register declaration once in app setup.

Escape hatch

If you intentionally need a paramName outside your registered route tree, wrap it with untypedUrlStateParamName(...).

import {
  untypedUrlStateParamName,
  useUrlState,
} from "@tilt-legal/cubitt-components/utilities/hooks";

export function DebugPanelInput() {
  const { value, setValue } = useUrlState({
    paramName: untypedUrlStateParamName("debug-panel"),
    paramDefaultValue: "",
  });

  return (
    <input
      value={value ?? ""}
      onChange={(event) => void setValue(event.target.value)}
    />
  );
}

Lost type safety

This helper explicitly opts that usage out of TanStack Router key checking. TypeScript can no longer autocomplete known keys or catch typos and search-key refactors for that parameter. Prefer proper router registration whenever you can.

API

OptionTypeDescriptionDefault
paramNamestringURL parameter name. Narrows to known search keys when your router is registered.-
paramValueTControlled value (overrides URL state)-
paramDefaultValueTDefault value when the URL is empty-
onUrlValueChange(value: T | null) => voidCallback when the URL value changes-
paramClearOnDefaultbooleanRemove the parameter when the value equals defaulttrue
paramThrottlenumberThrottle URL writes in milliseconds-
paramDebouncenumberDebounce URL writes in milliseconds-
paramCodecUrlStateCodec<T>Custom codec for advanced data shapes (overrides automatic selection)auto

Type support

When you provide a default or controlled value, the hook infers the codec:

  • stringstringCodec
  • booleanbooleanCodec
  • numbernumberCodec
  • string[]stringArrayCodec with implicit []
  • number[]numberArrayCodec with implicit []
  • objects → createJsonCodec<T>(validator)

If you need a custom shape, pass paramCodec.

If your app's TanStack Router serializer leaves complex values as raw strings, prefer an explicit paramCodec built with createJsonCodec(..., { parseRawString }) rather than relying on any implicit fallback.

Validation boundaries

useUrlState only validates the single parameter it owns through its codec. Route-level search validation still belongs to the consuming app via TanStack Router validateSearch.

On this page