Since URL parameters can be regarded as a portion of an app's state, it's only reasonable to think of managing them in the React way of managing state: similarly to useState()
.
From this perspective, here's what handling the URL params state of, let's say, the root URL /
would look like:
export const App = () => {
- let [{coords}, setState] = useState({coords: {}});
+ let [{query}, setState] = useRouteState('/');
let setPosition = () => {
setState((state) => ({
...state,
- coords: {
+ query: {
x: Math.floor(100 * Math.random()),
y: Math.floor(100 * Math.random()),
},
}));
};
return (
<main>
- <Shape x={coords.x} y={coords.y}/>
+ <Shape x={query.x} y={query.y}/>
<p><button onClick={setPosition}>Move</button></p>
</main>
);
};
The similarity of this code to the React's useState()
setup makes it familiar right away and streamlines the migration from local state to the URL params state, so it looks like a way to go.
This perspective fits well with the minimalist approach to routing I outlined in a recent post. The useRouteState()
hook from the code above is actually part of this approach, so now we can turn this code into a live demo.
In this approach, the route state can also be handled with preciser typing by introducing a type-safe URL builder url()
and a zod
-powered URL schema (as described in another post), although it's completely optional:
import {useRouteState} from '@t8/react-router';
import {createURLSchema} from 'url-shape';
import {z} from 'zod';
const {url} = createURLSchema({
'/shapes/:id': {
params: z.object({
id: z.coerce.number(),
}),
query: z.optional(
z.object({
x: z.coerce.number(),
y: z.coerce.number(),
})
),
},
});
export const ShapeSection = () => {
let [{params, query}, setState] = useRouteState(url('/shapes/:id'));
let setPosition = () => {
setState((state) => ({
...state,
query: {
x: Math.floor(100 * Math.random()),
y: Math.floor(100 * Math.random()),
},
}));
};
let resetPosition = () => {
setState({params});
};
return (
<main>
<h1>Shape {params.id}</h1>
<Shape x={query.x} y={query.y} n={params.id + 2}/>
<p>
<button onClick={setPosition}>Move</button>{' '}
<button onClick={resetPosition}>Reset</button>
</p>
</main>
);
};
Compared to the first example, we've essentially added a URL schema definition with createURLSchema()
and replaced the fixed URL '/'
passed to useRouteState()
as a parameter with the typed URL pattern url('/shapes/:id')
. These changes resulted in params
and query
being resolved with preciser typing (hover over them in the playground to see their types match the schema). The structure of the code is still pretty much the same, with the route state handling equally similar to React's useState()
.
More aspects of the discussed approach to routing are covered in the package overview.
By the way, if you're wondering if global/shared state can be managed in the useState()
-like manner as well, I've got a post on that, too.
Top comments (0)