react-zod-url-state
v0.1.3
Published
Schema-driven URL ↔ state sync for filters, tables & dashboards. Type-safe, SSR-friendly URL state management.
Downloads
13
Maintainers
Readme
React Zod URL State
Stop manually syncing component state with URL parameters. This library automatically keeps your filters, sorting, and pagination in sync with the URL using TypeScript schemas. No more broken back buttons, lost filter state on refresh, or hand-written query parameter parsing.
Installation
npm install react-zod-url-stateBefore 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
/products?q=shoes&page=2&sort=price&inStock=true&categories=sneakers,bootsFramework Support
Next.js
useNextUrlState(schema)- Works with App Router and Pages Router
React Router
useReactRouterUrlState(schema)- 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.
- ✅ Back/Forward Navigation: Works seamlessly with browser navigation
- ✅ SSR Friendly: Server-side rendering support for all major frameworks
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:
schema: Zod schema defining your state structureoptions?: Optional configuration objectmode?: 'push' | 'replace'- History behavior (default: 'replace')debounceMs?: number- Debounce delay in milliseconds (default: 0)scope?: 'search' | 'hash'- URL location (default: 'search')
Returns: DefinedUrlState<T> with methods:
readFrom(searchParams)- Parse URL params to typed statetoSearchParams(state)- Serialize state to URLSearchParamsschema- The Zod schemaoptions- Configuration options
Example:
const filters = defineUrlState(z.object({
q: z.string().default(""),
page: z.number().default(1),
}), {
debounceMs: 300,
mode: "replace"
});useUrlState(definition, options?)
React hook for syncing component state with URL parameters automatically.
Parameters:
definition: Result from defineUrlState()options?: Override options from definition
Returns: [state, setState] tuple:
state- Current URL state (fully typed)setState(updates)- Update state (merges with current)
Example:
const [state, setState] = useUrlState(filters);
// Read current state
console.log(state.q); // fully typed
// Update state (URL updates automatically)
setState({ q: "new search", page: 1 });useShareLink(definition)
Hook for generating and copying shareable URLs with current state.
Parameters:
definition: Result from defineUrlState()
Returns: Object with methods:
getShareLink(baseUrl?)- Generate shareable URLcopyShareLink(baseUrl?)- Copy URL to clipboard (returns Promise)
Example:
const { getShareLink, copyShareLink } = useShareLink(filters);
const shareUrl = getShareLink(); // Current page with state
const customUrl = getShareLink('https://myapp.com/search');
await copyShareLink(); // Copies to clipboarduseResetUrlState(definition, options?)
Hook for resetting URL state to schema defaults.
Returns: () => void - Function to reset state to defaults
Example:
const resetFilters = useResetUrlState(filters);
<button onClick={resetFilters}>
Clear All Filters
</button>Framework-Specific Hooks
Next.js
useNextUrlState(definition)useNextShareLink(definition)useNextResetUrlState(definition)
React Router
useReactRouterUrlState(definition)useReactRouterShareLink(definition)useReactRouterResetUrlState(definition)
SSR Helpers
Server-side utilities for reading and validating URL state.
getStateFromAppRouter(definition, searchParams)- Next.js App Router - read state from searchParams propgetStateFromLoader(definition, request)- React Router/Remix - read state from loader requestvalidateUrlState(definition, params)- Returns{ isValid, data, errors }for validation
License
MIT
