Inspared by this Lee Robinson tweet
Table of contents
Open Table of contents
From useState
to URL state.
Rather than using client-side React state, we can instead lift state up to the URL for color, size, and even the selected image.
Dealing with state management in our applications is often called as one of the few genuinely challenging aspects of development. Yet, it seems we may be making it harder on ourselves by overlooking one of the most powerful state managers readily available: the URL.
In my own experiences, particularly during my time working on internal tools, I found immense value in using the URL for state management. The ability to store complex search queries and configurations directly in the URL parameters not only provided a centralized location for global state but also eased collaboration within the team through simple URL sharing.
Examples
// ProductPage.js
import { useRouter } from 'next/router';
import { useState, useEffect } from 'react';
const ProductPage = () => {
const router = useRouter();
const [color, setColor] = useState('white');
const [size, setSize] = useState('medium');
useEffect(() => {
// Update URL with color and size when they change
router.push(`/product?color=${color}&size=${size}`);
}, [color, size]);
return (
<div>
<h1>Product Page</h1>
<p>Color: {color}</p>
<p>Size: {size}</p>
{/* UI components to update color and size */}
</div>
);
};
export default ProductPage;
One such example involves a rebuilt e-commerce application using Next.js Commerce. By utilizing the app router, I demonstrate how the URL can store state dynamically. For instance, changing the color and size of a shirt updates the URL, making it not just a global state repository but also a tool for meaningful URL sharing.
// SearchPage.js
import { useRouter } from 'next/router';
import { useState, useEffect } from 'react';
const SearchPage = () => {
const router = useRouter();
const [query, setQuery] = useState('');
useEffect(() => {
// Update URL with query parameter when it changes
router.push(`/search?query=${query}`);
}, [query]);
return (
<div>
<h1>Search Page</h1>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search products"
/>
{/* Display search results */}
</div>
);
};
export default SearchPage;
A similar approach is applied to a search page, where search queries are persisted in the URL. Whether filtering products or refreshing the page, the URL remains a reliable source of truth for the current state.
Now, let’s delve into the technical aspect. The programming model is surprisingly straightforward. In a Next.js search component, an input field takes its default value from a useSearchParams hook. Upon user interaction, a function is triggered to update the search params in the URL, demonstrating a simple yet effective approach.
- The presented solution may not be the most type-safe, and it should be regarded more as a foundation for further improvements rather than as production-ready code.
Conclusion
In conclusion, URL-based state management, often overlooked, offers a range of benefits from improved server rendering patterns to reliable URL sharing and true global access to data.