Skip to content

How to persist UI state on page reload

Posted on:May 14, 2025 at 05:15 AM

How to persist UI state on page reload

Recently while building a product listing page in my React app, I ran into a frustrating issue.

I had filters, pagination, and tabs working beautifully using React state. But the moment a user refreshed the page, all their selections — like the chosen category or the current page — were gone. Even worse, they couldn’t share or bookmark the page to return to the same view later.

That’s when I realized I needed to store some UI state in the URL.

I had something like this:

const [category, setCategory] = useState("All");

This worked… until:

In all those cases, React state reset, and the user had to re-select their filters.


The Solution: Sync State with the URL

To fix this, I used the useSearchParams hook from React Router.

const [searchParams, setSearchParams] = useSearchParams();
const category = searchParams.get("category") || "All";

The useSearchParams hook returns an array with two elements:

The .get() method reads the current value of a query parameter — in this case, category — directly from the URL.

For example, if the URL is:

/products?category=outdoor

Then:

searchParams.get("category"); // returns "outdoor"

This allows you to access state from the URL, just like you’d read from React state — but it’s now persistent, even across page refreshes or direct links.


Updating the URL with setSearchParams

When I want to update the query parameters, I use setSearchParams():

setSearchParams({ category: "indoor" });

It updates the URL to:

/products?category=indoor

But here’s the best part:

It’s like using useState, but with persistent, shareable state.


Managing Multiple Query Parameters

Sometimes you’ll want to manage more than one piece of state — like a category filter and a page number — in the URL at the same time.

Here’s how to update multiple query parameters at once:

setSearchParams({
  category: "indoor",
  page: 2,
});

This will update the URL to:

/products?category=indoor&page=2

You can also use the functional form when you want to change just one param (like category) without losing others (like page):

setSearchParams(prevParams => {
  const newParams = new URLSearchParams(prevParams);
  newParams.set("category", "indoor");
  return newParams;
});

Why Use .set()?

The .set() method lets you update a specific query parameter while preserving the others. It’s especially useful when:


My Real Example: Filter Products by Category

Here’s how I rewrote my component:

// Product component
...
const [searchParams, setSearchParams] = useSearchParams();

useEffect(() => {
  async function getPlantData() {
    try {
      const queryString = new URLSearchParams(searchParams).toString();
      const res = await fetch(\`http://localhost:8004/plants?\${queryString}\`);
      const { paginatedPlants, totalPages } = await res.json();

      setData(paginatedPlants);
      setTotalPages(totalPages);
    } catch (error) {
      console.error('Error fetching plants:', error);
    }
  }

  getPlantData();
}, [searchParams]);

...

And to handle category selection and page updates:

function handleCategory(e) {
  const category = e.target.value;

  setSearchParams(prevParams => {
    const newParams = new URLSearchParams(prevParams);
    newParams.set("category", category);
    newParams.set("page", 1); // reset page to 1 when category changes
    return newParams;
  });
}

function handleNextPage() {
  const currentPage = Number(searchParams.get("page") || 1);

  setSearchParams(prevParams => {
    const newParams = new URLSearchParams(prevParams);
    newParams.set("page", currentPage + 1);
    return newParams;
  });
}

What This Did for Me


It’s Great For:


Final Thoughts

Before useSearchParams, I was relying solely on internal React state — which worked, until it didn’t.

This little hook from React Router helped me bridge the gap between the browser URL and my component logic, keeping everything in sync and user-friendly.

If you’re building anything with filters, pagination, or tabbed views — and you want it to persist across refreshes and be shareable — I highly recommend giving useSearchParams a try.