skip to Main Content

I met react-hooks/exhaustive-deps lint error warning. Here is my code

const fetchData = async pageNumber => {
    if (pageNumber > totalPages) {
      return;
    }

    try {
      setIsLoading(true);
      // ... call api
      const content = response.data.content;
      if (pageNumber === 1) {
        setTotalPages(response.data.totalPages);
        setActivityData(content);
      } else {
        setActivityData([...activityData, ...content]);
      }
      setCurrPage(currPage + 1);
    } catch (e) {
      console.log(e);
    } finally {
      // ...
    }
  };

useEffect(() => {
    fetchData(currPage);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [currPage]);

useEffect(() => {
  setCurrPage(1);
  setActivityData([]);
  setTotalPages(1);
}, [activityStatus]);

These functions are designed to display the activityData paginatedly.

If I put fetchData in dependencies, it will raise another error saying this function will re-render. I’ve seen other posts suggesting putting fetchData inside useEffect, but I can’t do it in my code.

  1. What’s wrong with my way of using useEffect?
  2. Is there a better way to manage pagination and display it in a flow?

2

Answers


  1. I used to put the // eslint-disable-next-line react-hooks/exhaustive-deps everywhere, but now I know that it’s not (always) the case.

    When I see an EsLint error complaining about something, usually I turn on my brain and I’m thinking: "How can I do it better in order to remove this problem?"

    In your case, you have a useEffect that triggers when currPage changes. This is causing the loading to become true, gets the content and update the page number. Let’s try to refactor it, separating all the things into two important steps:

    1. You need to obtain data
    2. You set your component
    // OUTSIDE COMPONENT
    async function getData(pageNumber: number) {
      // Your logic here
      return await fetchDataFromSomewhere();
    }
    
    // INSIDE COMPONENT
    const YourComponent = () => {
      const [loading, setLoading] = useState(false); // Not loading yet
      const [data, setData] = useState([]); // Empty array to start with
      const [currentPage, setCurrentPage] = useState(1); // I'm on page 1 already
      const [maxPages, setMaxPages] = useState(1); // 1 Page so for now 1 page max
    
      // Every time page changes (so also the first time this will load)
      useEffect(() => {
       setLoading(true);
       getData(currentPage).then((result) => {
         // Ok!
         setData(result);
         if (currentPage === 1) {
           setMaxPages(result.pages);
         }
         setLoading(false);
       }).catch(() => {
         // Oh no! Handle error! And then...
         setLoading(false);
       })
      }, [currentPage]);
    
      const goToNextPage = () => {
        // Check for max pages
        setCurrentPage(currentPage + 1);
      }
    
      const goToPreviousPage = () => {
        // Check for min pages
        setCurrentPage(currentPage -1);
      }
    
      // Other stuff
    }
    
    
    Login or Signup to reply.
  2. I’ll answer your question 1 – question 2 is more opinion-based and more appropriate for a different Stack Exchange (like Code Review).

    TL;DR: wrap your function in useCallback.

    When you define a function in the body of a component, it will get recreated every time the component is computed for possible re-rendering. For many functions, that’s fine; the cost of recreating the function is minimal.

    However, everything changes once an effect depends on that function. Since the function is recreated, it fails a referential equality check. This causes the infinite loop:

    1. Function is created
    2. useEffect fires, since the function has changed
    3. Component body re-runs, recreating the function again
    4. Loop back to step 1

    This is why useCallback exists. It caches the defined function so that it will only be recreated if one of its dependencies changes. This means the function passes the equality check and won’t cause the effect to fire again.

    1. Function is created
    2. useEffect fires, since the function has changed
    3. Component body re-runs, but the function is not recreated
    4. useEffect doesn’t fire again, loop is prevented

    In your code this would look something like:

    const fetchData = useCallback(async pageNumber => {
      // ...
    }, [
      // eslint will tell you what the function should depend on
    ]);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search