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
| Option | Type | Description | Default |
|---|---|---|---|
paramName | string | URL parameter name. Narrows to known search keys when your router is registered. | - |
paramValue | T | Controlled value (overrides URL state) | - |
paramDefaultValue | T | Default value when the URL is empty | - |
onUrlValueChange | (value: T | null) => void | Callback when the URL value changes | - |
paramClearOnDefault | boolean | Remove the parameter when the value equals default | true |
paramThrottle | number | Throttle URL writes in milliseconds | - |
paramDebounce | number | Debounce URL writes in milliseconds | - |
paramCodec | UrlStateCodec<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:
string→stringCodecboolean→booleanCodecnumber→numberCodecstring[]→stringArrayCodecwith implicit[]number[]→numberArrayCodecwith 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.