idan noyshul
idan noyshul

Reputation: 169

Infinity loops with useEffect and useCallback - Reactjs

somehow I got an infinity loop, the weird situation that I did solve it but I got a warning and I wish to fix the warning.

this is code that work:

import { ArrowDropDown, ArrowRight } from "@material-ui/icons";
import React, { useState, useEffect, useCallback } from "react";
import "./tree.css";

const Tree = ({ explorer }) => {
  const [expand, setExpand] = useState(true);
  const [arrow, setArrow] = useState(false);

  const stateHandler = () => {
    setExpand(!expand);
    setArrow(!arrow);
  };

  useEffect(() => {
    //this function will display only the first Tree as init the page.
    stateHandler();
  }, []);

  return (
    <div>
      <div className="treeInfo">
        {arrow ? (
          <ArrowDropDown className="treeIcon" />
        ) : (
          <ArrowRight className="treeIcon" />
        )}

        <span className="treeTitle" onClick={stateHandler}>
          {explorer.name}
        </span>
      </div>

      <div
        style={{
          display: expand ? "block" : "none",
          paddingLeft: 20,
          cursor: "pointer",
        }}
      >
        {explorer.items.map((explore) => {
          return <Tree key={explore.id} explorer={explore} />;
        })}
        {/* {explorer.items.map((explore) => (
          <Tree explorer={explore} />
        ))} */}
      </div>
    </div>
  );
};

export default Tree;

and this is the warning:

src\components\Tree\Tree.js
  Line 17:6:  React Hook useEffect has a missing dependency: 'stateHandler'. Either include it or remove the dependency array  react-hooks/exhaustive-deps

Search for the keywords to learn more about each warning.
To ignore, add // eslint-disable-next-line to the line before.

WARNING in src\components\Tree\Tree.js
  Line 17:6:  React Hook useEffect has a missing dependency: 'stateHandler'. Either include it or remove the dependency array  react-hooks/exhaustive-deps

webpack compiled with 1 warning

**

**

result

well as I can read I got this warning because useEffect got not dependencies, when I add the stateHandler dependencies I got an infinity loop, so I add a callback function but it still doesn't solve the infinity loop. this is the code with the useCallback (its the same code, just with useCallback and a bit of configure of the useEffect):

 const initTree = useCallback(() => {
    setExpand(!expand);
    setArrow(!arrow);
  }, [setExpand, setArrow, arrow, expand]);

  useEffect(() => {
    //this function will display only the first Tree as init the page.
    initTree();
  }, [initTree]);

Upvotes: 3

Views: 664

Answers (2)

Cesare Polonara
Cesare Polonara

Reputation: 3860

You need to change this:

 const initTree = useCallback(() => {
    setExpand(!expand);
    setArrow(!arrow);
  }, [setExpand, setArrow, arrow, expand]);

to this:

 const initTree = useCallback(() => {
    setExpand(e => !e);
    setArrow(a => !a);
  }, []);

Otherwise what happens is this:

  1. The component renders, initTree variable is initialized with a function, the Effect is run and setExpand and setArrow are called.
  2. That triggers a new render
  3. The useCallback hook checks if the deps of initTree have changed and yes, arrow and expand have changed indeed, hence initTree variable is updated with a new function
  4. The effect checks if initTree has changed from the previous render, and yes, it has changed, hence the effect executes again calling initTree again.
  5. There you are stuck in an infinite render loop.

Eslint shouldn't complain of missing deps if you don't put the setState in deps, since they do not change during renders, unless you are passing them through props.

Upvotes: 5

Aneesh
Aneesh

Reputation: 1715

If you are ok with having another state, you can simply set something like a loading state and make the useEffect depend on that

State would look like this:

const [expand, setExpand] = useState(true);
const [arrow, setArrow] = useState(false);
const [loading, setLoading] = useState(true);

And then based on this, useEffect would change as follows:

useEffect(() => {
    //this function will display only the first Tree as init the page.
    stateHandler();
    setLoading(false);
}, [loading]);

Once loading is set to false, it's value is not going to change and the useEffect will not be triggered indefinitely

Upvotes: 1

Related Questions