How to Fix: Return to previous page How to keep page status

6 min read

The state is not actually being “lost” when you go back. In this case, the previous page is being recreated, so its local UI state resets to the initial values. That usually happens when list filters, selected tabs, scroll position, or fetched data live only inside the page component instead of being preserved in the URL, a shared store, or a cache layer.

Understanding the Root Cause

When a user navigates from a list page to a detail page and then returns, developers often expect the previous screen to appear exactly as it was. But in many React-based routing setups, that only works if the page state is stored somewhere that survives navigation.

There are three common reasons this bug appears:

  1. Component-local state is destroyed on route change. If the page uses useState for filters, pagination, active tabs, or selected items, those values disappear when the component unmounts.
  2. The router remounts the page instead of restoring it. Going back may trigger a fresh render path, especially if the route is recreated or wrapped in logic that resets state.
  3. Important UI state is not encoded in the URL. If the current page, query, selected category, or sort order is not saved as search params, the app has no durable source of truth to rebuild the screen.

So the real fix is not “force the browser to remember everything.” The fix is to move important page status into a persistence layer that survives navigation. For this issue, the most reliable solution is to store page status in route query parameters and restore UI from them when the user returns.

Step-by-Step Solution

The safest pattern is:

  1. Store the list page state in the URL.
  2. Read that state when the page loads.
  3. When navigating to the detail page, keep the current query string.
  4. On back navigation, the page rebuilds itself from the same URL state.

1. Move page status into search params

If your page has state like current tab, page number, search text, or filters, sync it to the URL.

import { useSearchParams, useNavigate } from "react-router-dom";
import { useMemo } from "react";

export default function ListPage() {
  const [searchParams, setSearchParams] = useSearchParams();
  const navigate = useNavigate();

  const page = Number(searchParams.get("page") || "1");
  const keyword = searchParams.get("keyword") || "";
  const status = searchParams.get("status") || "all";

  const queryState = useMemo(() => ({ page, keyword, status }), [page, keyword, status]);

  const updateParams = (next) => {
    const params = new URLSearchParams(searchParams);

    Object.entries(next).forEach(([key, value]) => {
      if (value === "" || value == null) {
        params.delete(key);
      } else {
        params.set(key, String(value));
      }
    });

    setSearchParams(params);
  };

  const handleKeywordChange = (value) => {
    updateParams({ keyword: value, page: 1 });
  };

  const handleStatusChange = (value) => {
    updateParams({ status: value, page: 1 });
  };

  const handlePageChange = (value) => {
    updateParams({ page: value });
  };

  const goToDetail = (id) => {
    navigate(`/detail/${id}?${searchParams.toString()}`);
  };

  return (
    <div>
      <input
        value={queryState.keyword}
        onChange={(e) => handleKeywordChange(e.target.value)}
        placeholder="Search"
      />

      <select
        value={queryState.status}
        onChange={(e) => handleStatusChange(e.target.value)}
      >
        <option value="all">All</option>
        <option value="open">Open</option>
        <option value="closed">Closed</option>
      </select>

      <button onClick={() => handlePageChange(page - 1)} disabled={page <= 1}>
        Prev
      </button>
      <span>Page {page}</span>
      <button onClick={() => handlePageChange(page + 1)}>Next</button>

      <button onClick={() => goToDetail(123)}>Open Detail</button>
    </div>
  );
}

2. Preserve the query string when opening the next page

This is the part many implementations miss. If the detail page link does not carry the current search params, the browser can return to a URL without the state you need.

import { Link, useLocation } from "react-router-dom";

function ItemLink({ id }) {
  const location = useLocation();

  return (
    <Link to={`/detail/${id}${location.search}`}>
      Open detail
    </Link>
  );
}

3. Use browser back instead of hardcoded navigation to a clean route

If your detail page uses a button like navigate("/list"), you are not returning to the exact previous history entry. You are creating a new navigation target, which often drops state. Prefer true back navigation when appropriate.

import { useNavigate } from "react-router-dom";

export default function DetailPage() {
  const navigate = useNavigate();

  return (
    <button onClick={() => navigate(-1)}>
      Back
    </button>
  );
}

If you must navigate explicitly, include the original search params:

import { useLocation, useNavigate } from "react-router-dom";

export default function DetailPage() {
  const navigate = useNavigate();
  const location = useLocation();

  const goBackToList = () => {
    navigate(`/list${location.search}`);
  };

  return <button onClick={goBackToList}>Back to list</button>;
}

4. Restore fetched data from cache if needed

If the issue is not only filters but also reloading remote data, use a caching layer such as React Query, SWR, or route-level data caching. URL params restore the view state; a cache prevents unnecessary refetching and flicker.

import { useQuery } from "@tanstack/react-query";

function useListData(page, keyword, status) {
  return useQuery({
    queryKey: ["items", page, keyword, status],
    queryFn: () => fetchItems({ page, keyword, status }),
    staleTime: 60 * 1000,
    keepPreviousData: true,
  });
}

5. Preserve scroll position when returning

Sometimes the data and filters are correct, but the page jumps to the top. That is a separate state problem. Depending on your router, you may need built-in scroll restoration or a manual solution.

import { useEffect } from "react";

export default function ListPage() {
  useEffect(() => {
    const savedY = sessionStorage.getItem("list-scroll-y");
    if (savedY) {
      window.scrollTo(0, Number(savedY));
    }

    return () => {
      sessionStorage.setItem("list-scroll-y", String(window.scrollY));
    };
  }, []);

  return <div>...</div>;
}

For this type of bug, use this decision rule:

  • URL params for filters, tabs, sorting, pagination, and searchable state.
  • Query cache for server data.
  • sessionStorage for scroll position or temporary UI restoration.
  • Global state only if the state must be shared across unrelated pages.

Common Edge Cases

  1. Using navigate(‘/list’) instead of navigate(-1)

    This creates a fresh route entry and often resets everything unless you manually append the original query string.

  2. State stored only in useState

    If the component unmounts, that state is gone. This is the most common root cause.

  3. Search params overwritten accidentally

    When updating one filter, developers sometimes rebuild the query string from scratch and delete other params such as page or sort.

  4. Detail page link drops the current search string

    If the link to the detail page is just /detail/123, the browser history may not preserve enough context to reconstruct the previous list state.

  5. Refetching causes UI flicker

    Even with correct routing, missing cache configuration can make the page feel reset because loading placeholders appear again.

  6. Scroll restoration still feels broken

    Filters may restore correctly while the list position does not. Save and restore scroll independently.

  7. SSR or hydration differences

    In frameworks that render on the server first, make sure the initial UI reads from the same URL-derived state on both server and client.

FAQ

Should I use local state or URL params for page status?

Use URL params for any state that should survive refresh, back navigation, sharing, or deep linking. Keep purely visual transient state in local component state.

Why does browser back still not restore my exact list view?

Because only the history entry is restored, not your destroyed React component state. If the state was never persisted in the URL, cache, or storage, the page rebuilds with defaults.

Is global state management the best fix for this issue?

Not usually. Global stores can help, but for list filters, pagination, and search, the URL is the best source of truth. It is simpler, shareable, and works naturally with browser navigation.

If you want the most stable fix for the reproduced issue, treat the previous page status as navigation state, not just component state. Persist it in the URL, preserve it when linking to the next page, and use cache or session storage for data and scroll restoration. That combination keeps the page looking unchanged when the user returns.

For routing behavior details, see the React Router documentation. If your reproduction uses another router, apply the same principle: persist the important state outside the page instance.

Leave a Reply

Your email address will not be published. Required fields are marked *