Skip to content
👉All tips here

Why Your React State Should Live in the URL

react state in url frontend tips

Your users bookmark a filtered product list, share the link, but when someone clicks it then bazinga the filters are gone. Sound familiar? The solution is simpler than you think: store that state in the URL.

Imagine you have a product dashboard with filters, search, and pagination. Users want to:

  • Refresh the page and keep their current view
  • Share filtered results with teammates
  • Use browser back/forward buttons naturally

With traditional useState, all this state vanishes on reload.

Your State Storage Options

In Memory

Fast but disappears on refresh

const [searchQuery, setSearchQuery] = useState('');
const [showFilters, setShowFilters] = useState(false);

Problem: disappears as soon as you refresh.


Browser Storage

Persists but can’t be shared

const [searchQuery, setSearchQuery] = useState(() => 
  localStorage.getItem('searchQuery') || ''
);

useEffect(() => {
  localStorage.setItem('searchQuery', searchQuery);
}, [searchQuery]);

Problem: users can’t share this state — it only exists for them.


URL

Persistent, shareable, and navigation-friendly, let me show you what they would actually do in a real component:

const [searchParams, setSearchParams] = useSearchParams();
const searchQuery = searchParams.get('q') || '';
const showFilters = searchParams.get('filters') === 'true';

const updateSearch = (query) => {
  setSearchParams(prev => ({ ...Object.fromEntries(prev), q: query }));
};

const toggleFilters = () => {
  setSearchParams(prev => ({ 
    ...Object.fromEntries(prev), 
    filters: showFilters ? 'false' : 'true' 
  }));
};

return (
  <div>
    <input 
      value={searchQuery} 
      onChange={(e) => updateSearch(e.target.value)}
      placeholder="Search products..."
    />
    
    <button onClick={toggleFilters}>
      {showFilters ? 'Hide' : 'Show'} Filters
    </button>
    
    {showFilters && (
      <div>Filter options here...</div>
    )}
  </div>
);

Now, refreshing, bookmarking, or sharing the page keeps both the search query and filter visibility in sync with the URL.

Here is another example by using the React Router:

import { useSearchParams } from 'react-router-dom';

function ProductFilter() {
  const [searchParams, setSearchParams] = useSearchParams();
  const category = searchParams.get('category') || 'all';
  
  const handleCategoryChange = (newCategory) => {
    setSearchParams({ category: newCategory });
  };

  return (
    <select value={category} onChange={(e) => handleCategoryChange(e.target.value)}>
      <option value="all">All Products</option>
      <option value="electronics">Electronics</option>
      <option value="books">Books</option>
    </select>
  );
}

Why URLs Win (most of the time)

✅ Benefits:

  • Shareable: Send app.com/products?category=electronics&sort=price to a colleague
  • Bookmarkable: Users can save their exact view
  • SEO-friendly: Search engines can index different states
  • Browser-native: Back/forward buttons work as expected

❌ Limitations:

  • URL length limits: Don’t store large objects
  • String-only: Requires parsing for complex data
  • Public: Sensitive data doesn’t belong here

The Bottom Line

URL state isn’t always the answer, but it’s often the right answer for user-facing application state. Your users will thank you when they can bookmark that perfect filtered view or share it with their team without losing their work.

Next time you reach for useState, ask yourself: “Would this be better in the URL?”