JAVASCRIPT
Synchronize React State with URL Query Parameters
Learn to build a `useQueryParamsSync` hook to read and write React state directly to URL query parameters, enabling shareable and bookmarkable filtered views.
import { useState, useEffect, useCallback } from 'react';
import { useLocation, useNavigate } from 'react-router-dom'; // Assuming react-router-dom v6+
function useQueryParamsSync(key, defaultValue) {
const location = useLocation();
const navigate = useNavigate();
const queryParams = new URLSearchParams(location.search);
const [value, setValue] = useState(() => {
const paramValue = queryParams.get(key);
if (paramValue) {
// Attempt to parse if it might be non-string (e.g., number, boolean, JSON)
try {
return JSON.parse(paramValue);
} catch (e) {
return paramValue; // If JSON parsing fails, treat as string
}
}
return defaultValue;
});
// Effect to update URL when state changes
useEffect(() => {
const currentParams = new URLSearchParams(location.search);
const serializedValue = typeof value === 'string' ? value : JSON.stringify(value);
if (serializedValue !== String(currentParams.get(key))) { // Compare serialized values
if (serializedValue !== String(defaultValue)) { // Only set if different from default
currentParams.set(key, serializedValue);
} else {
currentParams.delete(key); // Remove if value matches default
}
navigate(`?${currentParams.toString()}`, { replace: true });
}
}, [key, value, defaultValue, location.search, navigate]);
// Function to update both state and URL
const setSyncedValue = useCallback((newValue) => {
setValue(newValue);
}, []);
return [value, setSyncedValue];
}
// How to use it:
// // In your App.js or main router file:
// // import { BrowserRouter as Router } from 'react-router-dom';
// // <Router><MyComponent /></Router>
//
// function FilterableProductList() {
// const [category, setCategory] = useQueryParamsSync('category', 'all');
// const [priceMin, setPriceMin] = useQueryParamsSync('minPrice', 0);
//
// return (
// <div>
// <h1>Product List</h1>
// <p>Current Category: {category}</p>
// <button onClick={() => setCategory('electronics')}>Electronics</button>
// <button onClick={() => setCategory('books')}>Books</button>
// <p>Min Price: ${priceMin}</p>
// <input
// type="number"
// value={priceMin}
// onChange={(e) => setPriceMin(Number(e.target.value))}
// />
// </div>
// );
// }
How it works: The `useQueryParamsSync` hook allows a React component's state to be synchronized with a specific URL query parameter. It initializes the state from the URL if the parameter exists or uses a `defaultValue`. Any changes to the state trigger a `useEffect` to update the URL's query parameters without a full page reload, ensuring that the component's state is reflected in a shareable and bookmarkable URL. This example assumes `react-router-dom` v6+.