Skip to content

Common scenario with react hooks: how to change hook dependency without causing infinite render

I’m writing a React application and I have a common scenario that I would like to know how to solve from design point of view rather than with workarounds.

Often I’m in a situation where I have to change a hook dependency inside the hook itself.

In this case I have a table and I’m writing a component that handles the pagination. The three main variables that the pagination is based on are: the number of rows, the number of the page, the number of items per page. Here’s the hook:

  const [page, setPage] = useState(1);
  const [itemsPerPage, setItemsPerPage] = useState(5);

  useEffect(() => {
    let fixedItemsPerPage = itemsPerPage;
    let fixedPage = page;

    if (itemsPerPage > filteredRequests.length) {
      fixedItemsPerPage = filteredRequests.length;
      fixedPage = 1;
    }

    const paginatedFilteredRequests = filteredRequests.slice(
      fixedItemsPerPage * (fixedPage - 1),
      fixedItemsPerPage * fixedPage,
    );

    setVisibleRequests(paginatedFilteredRequests);
  }, [filteredRequests, itemsPerPage, page]);

So, anytime one of those variables change, I re-run the hook and I adjust the array of visibleRequests based on the pagination variables. The reason I need the auxiliar variables fixedItemsPerPage and fixedPage is because if I select a itemsPerPage that is higher than the length of the rows array the slice function will go out of bound, or if I select page 2 and then I adjust itemsPerPage to include all the items in the table I will still be in page 2 and it will show nothing instead of all the results.

Clearly this is a workaround, ideally I would like to set those variables with setItemsPerPage and setPage, but if I do that, I will cause an infinite render.

How can I solve the problem?

Answer

We’re missing some info here – What exactly do you want to happen if I set the page to 2, then set items per page to be higher than the total results count?
What about if I go to page 5, and set the items per page to something that results in 3 pages?

Assuming you want to re-set the page to the last available one (so 5 will turn into 3 in the example above), you can do the following:

const [page, setPage] = useState(1);
const [itemsPerPage, setItemsPerPage] = useState(5);


useEffect(() => {
  // No effect to run if there's no data
  if (filteredRequests.length === 0) return;

  // If the current page is too far forward
  if (itemsPerPage * (page - 1) >= filteredRequests.length) {
    const lastPage = Math.ceil(filteredRequests.length / itemsPerPage);
    setPage(lastPage);
  }
}, [itemsPerPage, page, filteredRequests])

const visibleRequests = useMemo(() => {
  return filteredRequests.slice(
    itemsPerPage * (page - 1),
    itemsPerPage * page
  );
}, [filteredRequests, itemsPerPage, page]);

I split the code in your useEffect into 2 hooks:

  • useEffect – To perform state validation for lastPage
  • useMemo – To calculate visibleRequests

The useEffect hook will be called twice in the event that you go overboard, since it is calling setPage, while page is a dependency of it. But that’s ok considering the logic inside – You will never cause an infinite loop, because after calling setPage within the useEffect, the subsequent render will not have problematic data

Also, it is ok to go over index with slice, it will simply return an empty array (which makes sense, because page 2 is empty if page 1 has all results in it)