Installation
Before vs After
❌ Without react-zod-url-state
import { useSearchParams, useRouter } from 'next/navigation';
import { useEffect, useState, useCallback } from 'react';
export function ProductFilters() {
const searchParams = useSearchParams();
const router = useRouter();
const [q, setQ] = useState('');
const [page, setPage] = useState(1);
const [sort, setSort] = useState('name');
const [inStock, setInStock] = useState(false);
// Sync URL to state on mount
useEffect(() => {
setQ(searchParams.get('q') || '');
setPage(parseInt(searchParams.get('page')) || 1);
setSort(searchParams.get('sort') || 'name');
setInStock(searchParams.get('inStock') === 'true');
}, [searchParams]);
// Sync state to URL
const updateUrl = useCallback((updates) => {
const params = new URLSearchParams(searchParams);
Object.entries(updates).forEach(([key, value]) => {
if (value === undefined || value === '') {
params.delete(key);
} else {
params.set(key, String(value));
}
});
router.replace(`?${params.toString()}`);
}, [searchParams, router]);
const handleSearch = (value) => {
setQ(value);
updateUrl({ q: value, page: 1 });
};
const handleSort = (value) => {
setSort(value);
updateUrl({ sort: value, page: 1 });
};
return (
<div>
<input
value={q}
onChange={(e) => handleSearch(e.target.value)}
/>
<select
value={sort}
onChange={(e) => handleSort(e.target.value)}
>
<option value="name">Name</option>
<option value="price">Price</option>
</select>
</div>
);
}
✅ With react-zod-url-state
import { defineUrlState, useUrlState, z } from "react-zod-url-state";
const filters = defineUrlState(z.object({
q: z.string().default(""),
page: z.number().int().min(1).default(1),
sort: z.enum(["name", "price"]).default("name"),
inStock: z.boolean().default(false),
}));
export function ProductFilters() {
const [state, setState] = useUrlState(filters);
return (
<div>
<input
value={state.q}
onChange={(e) => setState({
q: e.target.value,
page: 1
})}
/>
<select
value={state.sort}
onChange={(e) => setState({
sort: e.target.value,
page: 1
})}
>
<option value="name">Name</option>
<option value="price">Price</option>
</select>
</div>
);
}
Result: 85% less code, automatic type safety, zero boilerplate, and it handles arrays, dates, and complex objects automatically.
Quick Start
1. Define your schema
import { defineUrlState, z } from "react-zod-url-state";
export const filters = defineUrlState(z.object({
q: z.string().default(""),
page: z.number().int().min(1).default(1),
sort: z.enum(["name", "price"]).default("name"),
inStock: z.boolean().default(false),
categories: z.array(z.string()).default([]),
}));
2. Use in your component
import { useUrlState } from "react-zod-url-state";
export function ProductFilters() {
const [state, setState] = useUrlState(filters);
return (
<div>
<input
value={state.q}
onChange={(e) => setState({ q: e.target.value, page: 1 })}
placeholder="Search..."
/>
<select
value={state.sort}
onChange={(e) => setState({ sort: e.target.value })}
>
<option value="name">Name</option>
<option value="price">Price</option>
</select>
</div>
);
}
3. URL automatically updates
Framework Support
Next.js
Works with App Router and Pages Router
React Router
Full integration with React Router v6+
Server-Side Rendering
// Next.js App Router
export default function Page({ searchParams }) {
const state = filters.readFrom(searchParams);
// Use state for data fetching
}
// React Router loader
export async function loader({ request }) {
const state = getStateFromLoader(filters, request);
// Use state for data fetching
}
Core Features
Type Safety
Full TypeScript support with Zod schema validation. Catch errors at compile time.
Automatic Serialization
Handles arrays, booleans, numbers, dates, and enums automatically.
Debounced Updates
Prevent URL spam with configurable debouncing for text inputs.
Share URLs
Built-in helpers to generate shareable URLs with complete state.
Advanced Usage
Debounced Search
const filters = defineUrlState(schema, {
debounceMs: 300, // Wait 300ms before updating URL
mode: "replace" // Don't create history entries
});
Share Current State
const { copyShareLink } = useShareLink(filters);
<button onClick={() => copyShareLink()}>
Share Current Filters
</button>
Reset to Defaults
const resetState = useResetUrlState(filters);
<button onClick={resetState}>
Clear All Filters
</button>
API Reference
defineUrlState(schema, options?)
Creates a URL state definition with Zod schema validation and configuration options.
Parameters
mode?: 'push' | 'replace'
- History behavior (default: 'replace')•
debounceMs?: number
- Debounce delay in milliseconds (default: 0)•
scope?: 'search' | 'hash'
- URL location (default: 'search')Returns
readFrom(searchParams)
- Parse URL params to typed state•
toSearchParams(state)
- Serialize state to URLSearchParams•
schema
- The Zod schema•
options
- Configuration optionsExample
useUrlState(definition, options?)
React hook for syncing component state with URL parameters automatically.
Parameters
Returns
state
- Current URL state (fully typed)•
setState(updates)
- Update state (merges with current)Example
useShareLink(definition)
Hook for generating and copying shareable URLs with current state.
Parameters
Returns
getShareLink(baseUrl?)
- Generate shareable URL•
copyShareLink(baseUrl?)
- Copy URL to clipboard (returns Promise)Example
useResetUrlState(definition, options?)
Hook for resetting URL state to schema defaults.
Returns
Example
Framework-Specific Hooks
Optimized hooks for specific routing libraries.
Next.js
React Router
SSR Helpers
Server-side utilities for reading and validating URL state.