0

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?

3
  • Can't you simply remove the itemsPerPage and page from the dependency array? This use-effect always sets them, and does so based on the filteredRequests right. So you don't want the rendering to depend on them. You will still be able to read the up to date values. Commented Jul 20, 2021 at 10:02
  • if I removed them and then change the page number or the number of items per page the hook won't be called and I wouldn't see the new items in the table! Commented Jul 20, 2021 at 10:05
  • Is setVisibleRequests the state changing function for filteredRequests? Commented Jul 20, 2021 at 10:10

2 Answers 2

1

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)

Sign up to request clarification or add additional context in comments.

2 Comments

I think you forgot to use visibleRequests
You would use it when rendering the data, but you don't need it in the useEffect hook. I re-ordered them for clarification
0

You can use useRef instead of useState, to make sure you only update the reference.

Another thing you can do is the compare the previous state to the new changed state. This prevents infinite re-renders, by early returning if a change is meaningless.

Have a look in the official React.js FAQ: https://reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state

2 Comments

This is bad because useRef won't case a re-render, which you obviously want when changing the state here
"Compare the previous state to the new changed state. This prevents infinite re-renders, by early returning if a change is meaningless." - you can have both the state and the ref, to check previous value diff. That will still cause re-render, just only once pr. change

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.