@dipakshiroya/react-use-url-state
v0.1.2
Published
A smart React hook to sync deeply nested state with the URL query or hash with schema validation, versioning, and debounced updates.
Maintainers
Readme
@dipakshiroya/react-use-url-state
A smart React hook to sync deeply nested state with the URL query or hash, with debouncing, versioning, and schema validation.
Features
- Syncs React state to URL query parameters or hash.
- Handles deeply nested objects.
- Two-way sync: URL → state on load / popstate, and state → URL on change.
- Debounced URL updates.
- Versioning to detect incompatible state shapes.
- Optional schema validation / coercion.
- Supports two modes:
single(encodes full state in one base64 param)flat(flattened keys in query string)
Installation
npm install @dipakshiroya/react-use-url-state
or
yarn add @dipakshiroya/react-use-url-stateQuick Example
This example syncs a single query parameter q in the URL with a text input:
import React from "react";
import { useUrlState } from "@dipakshiroya/react-use-url-state";
export default function App() {
// Initial state { q: "" } will load from ?q=... if present
const [state, setState] = useUrlState<{ q: string }>({ q: "" });
return (
<div style={{ padding: 20 }}>
<h1>🔍 Search Demo</h1>
<input
type="text"
placeholder="Type to update ?q=..."
value={state.q}
onChange={(e) => setState({ q: e.target.value })}
style={{ padding: "8px", fontSize: "16px" }}
/>
<p>Current query: <b>{state.q}</b></p>
<p>Try typing something and see the URL update automatically!</p>
</div>
);
}Advanced Example: Nested Filters
import React from "react";
import { useUrlState } from "@dipakshiroya/react-use-url-state";
const defaultFilters = {
q: "",
page: 1,
perPage: 20,
filters: {
categories: [] as string[],
price: { min: 0, max: 200 },
showOutOfStock: false,
},
};
export default function FilterApp() {
const [filters, setFilters, flush] = useUrlState(defaultFilters, {
param: "state",
debounceMs: 300,
version: "v1",
mode: "single",
validator: (incoming) => {
if (!incoming || typeof incoming !== "object") return { ok: false };
const clean = { ...defaultFilters, ...(incoming as any) };
clean.page = Math.max(1, Number(clean.page) || 1);
clean.perPage = Math.max(1, Math.min(100, Number(clean.perPage) || 20));
if (clean.filters?.price) {
clean.filters.price.min = Number(clean.filters.price.min) || 0;
clean.filters.price.max = Number(clean.filters.price.max) || 0;
}
return { ok: true, value: clean };
},
});
return (
<div style={{ padding: 20 }}>
<h2>Advanced Filters Demo</h2>
<input
placeholder="Search"
value={filters.q}
onChange={(e) => setFilters({ ...filters, q: e.target.value })}
/>
<input
type="number"
value={filters.page}
onChange={(e) => setFilters({ ...filters, page: Number(e.target.value) })}
/>
<button onClick={() => flush()}>Flush to URL now</button>
<pre>{JSON.stringify(filters, null, 2)}</pre>
</div>
);
}How it works
- Typing in the input automatically updates the URL query string.
- Reloading the page preserves the state from the URL.
- Supports nested objects with optional schema validation.
- Debounced updates prevent excessive URL changes.
License
MIT © Dipak Shiroya
