reactjstypescriptpaginationreact-router-dombrowser-history

React pagination queries


I am building pagination for React project. The pagination itself does work when I click on prev or next page buttons, but I've got 2 problems, which I can't figure out.

  1. Browser button acts weird. I am at home page (page1) and click next page until I reach last page(page3). Now I open specific product page while at page 3 and when I click browser back button first time it correctly puts me back into page 3, but when I click again I expect to go back to page 2, but instead I am moved out of the website entirely and see some other website which I was browsing before.

I also get this warning in chrome dev tools:

Use of history.pushState in a trivial session history context, which maintains only one session history entry.

It seems like this warning indicated the problem, but how to save multiple history records?

  1. If I manually change search query in browser from "http://localhost:5173/?p=1" let's say to "http://localhost:5173/?p=3" nothing happens. I see that the page refreshes and I believe my new "?p=3" query is not persisted somehow so useEffect code does not run?

Code:

let [currentPage, setCurrentPage] = useState(getCurrentPage());
let [pagesCount, setPagesCount] = useState(0);
let [itemsCount, setItemsCount] = useState(0);
let [productData, UpdateProductData] = useState([]);
const [searchParams, setSearchParams] = useSearchParams();

// On init
useEffect(() => {
  getCurrentPage();
  setSearchParams({ p: `${currentPage}` });
  calcPagesCount();
  getData();
}, []);

// Updates screen if query is changed by browser back button or manually changing query in url bar
useEffect(() => {
  let CurrentPageFromQuery: number;
  CurrentPageFromQuery = Number(location.search.replace(/\D/g, ""));
  setCurrentPage(CurrentPageFromQuery);
  saveCurrentPage();
  getData();
}, [searchParams]);

// Updates query if next/prev page buttons are clicked
useEffect(() => {
  saveCurrentPage();
  getData();
  setSearchParams({ p: `${currentPage}` });
}, [currentPage]);

function prevPage() {
  if (currentPage !== 1) {
    getCurrentPage();
    setCurrentPage(currentPage - 1);
    saveCurrentPage();
  }
}

function nextPage() {
  if (currentPage !== pagesCount) {
    getCurrentPage();
    setCurrentPage(currentPage + 1);
    saveCurrentPage();
  }
}

function saveCurrentPage() {
  sessionStorage.setItem("current page", String(currentPage));
}

function getCurrentPage(): number {
  return Number(sessionStorage.getItem("current page") || 1);
}

async function calcPagesCount() {
  const api_url = `https://******&per_page=99`;
  const req = await fetch(api_url);
  const products = await req.json();
  setItemsCount(products.length);
  setPagesCount(Math.floor(products.length / 9));
}

async function getData() {
  const api_url = `https://******&per_page=9&page=${currentPage}`;
  const req = await fetch(api_url);
  const products = await req.json();
  UpdateProductData(products);
}

Solution

  • You should have only a single source of truth for what page you are on. Here you are using the URL search params, and a local currentPage state, and then sessionStorage on top of that. I think you also have too many side-effects and overloaded functions/handlers/callbacks.

    I suspect you don't need the currentPage state, you can read and update the p URLSearchParameter and synchronize to/from sessionStorage.

    Suggested Changes:

    Example:

    const [searchParams, setSearchParams] = useSearchParams();
    
    const [pagesCount, setPagesCount] = useState(0);
    const [itemsCount, setItemsCount] = useState(0);
    const [productData, UpdateProductData] = useState([]);
    
    // Read current page from search params, fallback to storage
    const currentPage = Number(searchParams.get("p")) ?? getCurrentPage();
    
    // Fetch data for the current page
    useEffect(() => {
      getData(currentPage);
      calcPagesCount();
      saveCurrentPage(currentPage);
    }, [currentPage]);
    
    function prevPage() {
      setSearchParams(searchParams => {
        const p = Number(searchParams.get("p")) || 1;
        searchParams.set("p", Math.max(1, p - 1));
        return searchParams;
      });
    }
    
    function nextPage() {
      setSearchParams(searchParams => {
        const p = Number(searchParams.get("p")) || 1;
        searchParams.set("p", Math.min(p + 1, pagesCount));
        return searchParams;
      });
    }
    
    function saveCurrentPage(currentPage: number) {
      sessionStorage.setItem("current page", String(currentPage));
    }
    
    function getCurrentPage(): number {
      return Number(sessionStorage.getItem("current page") || 1);
    }
    
    async function getData(currentPage: number) {
      const api_url = `https://******&per_page=9&page=${currentPage}`;
      const req = await fetch(api_url);
      const products = await req.json();
      UpdateProductData(products);
    }