react: React Hook useEffect has a missing dependency: 'xxx' ?

Hi, I am researching React Hook recently, it’s great.

But I have a problem, I did not find a suitable answer in the React Hook documentation and google to solve my problem.

When I used a function in useEffect and function component together, I encountered a warning that I did not specify a dependency in useEffect, as shown below:

image

Is there any way to solve my problem? I have thought about it for a long time.

What is the current behavior?

React Hook useEffect has a missing dependency: 'setCenterPosition'. Either include it or remove the dependency array. (react-hooks/exhaustive-deps)

Mini repro in codesandbox: https://codesandbox.io/s/trusting-kowalevski-oet4b

Any solution, thanks.

Code

function App() {
  const [elePositionArr, setElePositionArr] = useState([
    { left: 0, top: 0 },
    { left: 0, top: 0 },
    { left: 0, top: 0 }
  ]);
  const stageRef = useRef(null);
  const Stage = useRef({ w: 0, h: 0 });
  const currentIndex = useRef(0);

  useEffect(() => {
    // Record the size of the stage
    const stageW = stageRef.current.scrollWidth;
    const stageH = stageRef.current.scrollHeight;

    Stage.current = { w: stageW, h: stageH };

    const index = Math.floor(Math.random() * 3);
    currentIndex.current = index;
    setCenterPosition(index);
  }, []);

  // Centering a block element
  function setCenterPosition(index) {
    let cacheEle = elePositionArr;
    // calc center postion
    const centerOfLeft = Stage.current.w / 2 - 25;
    const centerOfTop = Stage.current.h / 2 - 25;

    cacheEle = cacheEle.map((item, i) => {
      const randomWNum = Math.floor(Math.random() * Stage.current.w) - 50;
      const randomHNum = Math.floor(Math.random() * Stage.current.h) - 50;
      const randomLeft = randomWNum <= 50 ? 50 : randomWNum;
      const randomTop = randomHNum <= 50 ? 50 : randomHNum;
      let newItem;

      if (index === i) {
        newItem = { left: centerOfLeft, top: centerOfTop };
      } else {
        newItem = { left: randomLeft, top: randomTop };
      }

      return newItem;
    });

    setElePositionArr(cacheEle);
  }

  function handleClickLi(index) {
    if (currentIndex.current !== index) {
      setCenterPosition(index);
      currentIndex.current = index;
    }
  }

  return (
    <div className="container">
      <div className="stage" ref={stageRef}>
        {elePositionArr.map((item, index) => (
          <div className="ele" key={index} style={item}>
            {index}
          </div>
        ))}
      </div>
      <ul className="nav">
        {elePositionArr.map((item, index) => (
          <li
            className={currentIndex.current === index ? "active-li" : ""}
            onClick={() => {
              handleClickLi(index);
            }}
            key={"li" + index}
          >
            {index}
          </li>
        ))}
      </ul>
    </div>
  );
}

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 4
  • Comments: 84

Commits related to this issue

Most upvoted comments

As suggested in the warning message, you can do like that

const initFetch = useCallback(() => {
    dispatch(fetchPosts());
  }, [dispatch]);

  useEffect(() => {
    initFetch();
  }, [initFetch]);

When thousands of devs face this same issue and the React dev team closes the thread and ignores everyone: 8d6

i’m interested for an explanation also. React Hook useEffect has a missing dependency: ‘dispatch’.

  useEffect(() => {
    axios.get('http://localhost:8000/api/future-registrations')
      .then((result) => {
        dispatch(initRegistrationsAction(result.data));
      });
  }, []);

I would propose to not show this warning when the second argument to useEffect() is [], b/c in that case the developer knows that this effect is only going to run once and therefore wants to use the initial prop values and doesn’t care if the prop changes on subsequent renders.

This seems to be quite a common use case - one that I have run into on my current project.

You want to run an effect hook just one time, but there IS a dependancy, although you only care about the state of that at startup. Currently using // eslint-disable-next-line react-hooks/exhaustive-deps, to turn off the linting rule but would be nice to not have to in this case.

This should answer your question:

https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies

@gaearon Did not solve my problem, my problem is more special, my run functionsetElePositionArr wants to fire on componentDidMount and handleClick, but the React Hook function does not meet my requirements and a warning appears.

Case 1: If we want the function to run on initialization as well as when specified parameters change in value (as the lint warning suggests), then we pass those parameters into the array.

Case 2: If we want the function to run exactly one time on initialization, then we use an empty array so that effect is only triggered once.

In Case 2, we are essentially listening for the componentDidMount event in accordance with the React Hook documentation specifications. The lint error is inconsistent with the React Hook documentation and developer expectations.

SOLUTION, (that worked for me):

const onInit = function(){ 
	console.log('initiated', Date.now());
}

useEffect(onInit, []);

I would prefer the issue to be fixed on linting side, but hey…

@fayway then whenver initFetch and dispatch value changed, useEffect’s callback will be execute. he wants to execute callback once

I’m having the same ESLint warning but in my case I want to make an async call when my component is mounted.

const ManageTagsModalBody: React.FC<IProps> = ({ children, getTags }) => {
  useEffect(() => {
    getTags();
  }, []);

  return <div>{children}</div>;
};
Line 12:  React Hook useEffect has a missing dependency: 'getTags'. Either include it or remove the dependency array. If 'getTags' changes too often, find the parent component that defines it and wrap that definition in useCallback  react-hooks/exhaustive-deps 

ManageTagsModalBody doesn’t depend on any data it’s only used as a wrapper to load data anytime the component is rendered. It’s worth noting the children component is using the data getTags has fetch.

I’m not sure what to add to the list of dependencies.

When thousands of devs face this same issue and the React dev team closes the thread and ignores everyone: 8d6

Pretty much this. Here I am giving hooks a try and I’m greeted with this warning which makes no sense and isn’t covered in any of the official docs. Really makes you wonder if hooks are ready for serious work when a basic use case like “please just run this effect only once” gives this much trouble.

If you want to run a function only once when the component loads and which takes in parameters then you can use useRef to avoid the warning. Something like this works:

  const InitialPropA= useRef(propA);
  useEffect(() => {
    myFunction(InitialPropA.current);
  }, [myFunction, InitialPropA]);

Same issue here. I just wanted to fire a function inside useEffect() once, having [] as second parameter does what I want, but it keeps giving this warning.

Same here. As simple as in https://github.com/facebook/react/issues/15865#issuecomment-506503377:

  useEffect(() => {
    dispatch(fetchPosts());
  }, []);

Is there a way to suppress this warning?

Why is this closed?

I believe an answer has been given somewhere… lol… anyway… the solution is to do like this: useEffect(() => { dispatch(); }, [dispatch]);

hope this helps!!

Why is this closed?

Have the same issue. I only want my fetch to fire on componentDidMount. Adding the dependency to the array results in repeatedly hitting the endpoint.

Can we just remove the empty array? Doing so seems to work (code still runs as expected, and warning is gone), but I dont know if there is any reason we shouldn’t do it that way.

useEffect(()=>{ myFunc(); } )

@robthedev Greate solution. We can even simplify the code If we use redux:

const dispatch = useDispatch();

// redux can guarantee that the dispatch function will not change between renders.
// so we don't need to wrap it with useCallback for now.
useEffect(() => {
  dispatch(actions());
}, [dispatch]);

Dan’s this article helps me a lot.

The solution to this problem is not to remove a dependency, instead, we can hoist functions that don’t need props or state outside of the component or, wrap them into useCallback where they’re defined.

For me, the error appeared in the following situation:

  useEffect(() => {
    props.getTasks()
  }, [])

I corrected it like this:

const { getTasks } = props
  useEffect(() => {
    getTasks()
  }, [getTasks])

This change seems a bit ridiculous and counter productive to be quite frank. Rather rollback to an earlier version and let you think this one through.

ぴえん

@kennylbj

After trying a bunch of different suggestions, this is what ultimately worked for me. I preferred not using eslint-disable, while trying to keep the code relatively simple for a newbie. This approach allows you to pass your dispatch function without invoking it inside of useCallback. Hope this helps.

// ... inside function component
const { dispatchFunc } = customHook();
const memoizeDispatchFunc = useCallback(dispatchFunc, []);

useEffect(() => {
  memoizeDispatchFunc();
}, [memoizeDispatchFunc]);

same problem. I want to imitate the behaviour of componentDidMount; I’m not interested on the warning; I don’t want to re-run the hook when anything changes

As suggested in the warning message, you can do like that

const initFetch = useCallback(() => {
    dispatch(fetchPosts());
  }, [dispatch]);

  useEffect(() => {
    initFetch();
  }, [initFetch]);

this also may create a memory leak in case of something like an API, it will make the call on an infinite loop.

What’s the point on adding the function into the dependency Array? What’s the point on those warnings? Kinda stupid

Best work-around: react-hooks/exhaustive-deps: “off” I have never found this eslint feature to be useful. Ever.

As suggested in the warning message, you can do like that

const initFetch = useCallback(() => {
    dispatch(fetchPosts());
  }, [dispatch]);

  useEffect(() => {
    initFetch();
  }, [initFetch]);

wow,非常感谢你🙏

Such a weird warning because I’ve never seen it before until today on a new project. It ruins the use case of having something called only once when component has mounted. There doesn’t seem to be any workaround except ignoring the lint warning.

Maybe it can be like this

const setCenterPosition = useRef(null)
setCenterPosition.current = useCallback( ()=>{} ,[deps])

effect(()=>{ setCenterPosition.current() },[setCenterPosition,otherDeps])

@kennylbj

After trying a bunch of different suggestions, this is what ultimately worked for me. I preferred not using eslint-disable, while trying to keep the code relatively simple for a newbie. This approach allows you to pass your dispatch function without invoking it inside of useCallback. Hope this helps.

// ... inside function component
const { dispatchFunc } = customHook();
const memoizeDispatchFunc = useCallback(dispatchFunc, []);

useEffect(() => {
  memoizeDispatchFunc();
}, [memoizeDispatchFunc]);

With me the above example does not work.

Get this error: TypeError: Cannot destructure property 'dispatchFunc' of 'componentDidMount(...)' as it is undefined.

@react-team: Happy Boilerplate Code. 😃 Can it be please logical and readable again… events should be simple to use… without having to write 5+ lines of logic just to call a function.

Same issue here. Strange rule.

Did anyone find a way to call a function inside a useEffect with empty array callback without warning and without adding bullshit code ?

React has the power to make easy things impossible. I love it.

This is a reasonably common issue, which can be solved by passing your dependency into the dependency array.

In the OP’s example it could be solved by passing setCenterPosition like this. In any of your cases, you would pass your dependency in the same place, this is not specific to his question.

carbon

If this warning is not handled it can cause memory leaks in your application as it can cause infinite loops and re-render issues.

In the case that you want an effect to only be ran once, Chris Coyier has an excellent example here

For more information though, as @gaearon said, it can be found here: https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies

This seems to be quite a common use case - one that I have run into on my current project.

You want to run an effect hook just one time, but there IS a dependancy, although you only care about the state of that at startup. Currently using // eslint-disable-next-line react-hooks/exhaustive-deps, to turn off the linting rule but would be nice to not have to in this case.

Closing Lint is just a temporary solution, the key to this problem is not enough to understand the hook.

What’s the point on adding the function into the dependency Array? What’s the point on those warnings? Kinda stupid

Please read an article on the topic. https://reacttraining.com/blog/when-to-use-functions-in-hooks-dependency-array/

My solution to this problem has been to simply stop using React Hooks for these cases. If you component needs lots of state logic and lifecycle events like componentDidMount that badly, just use a class component and save yourself the headache. Class components are perfectly fine, and if this thread proves anything, it’s that React Hooks are not ready to fully replace them when complex state logic or lifecycle events are needed (nor do they confer any advantage in most of these cases - is your code really that much better for having used React Hooks?).

I’ll be limiting my own use of React Hooks to simple ones like useState to set boolean flags and that sort of thing. If a component ever gets complex enough to need useEffect I treat that as a sign that maybe a class component is just a better fit.

(Edited for clarity).

🚀 Below code snippet works really well for me

On Component did mount

const onInit = () => getUsers()
useEffect(onInit, [])

On parameter change

const onInit = () => getUser(id)
useEffect(onInit, [id])

My issue kinda like this (sample code):

  const [userStatus, setUserStatus]: any = React.useState([]);
  const [userData, setUserData]: any = React.useState([]);

  useEffect(() => {
    setUserStatus(!userStatus);
    return () => {};
  }, [userData]);

So as in the code you want to change userStatus value when there’s a change in userData and in this case if you want to check userStatus value you have add it to deps:

  const [userStatus, setUserStatus]: any = React.useState([]);
  const [userData, setUserData]: any = React.useState([]);

  useEffect(() => {
    setUserStatus(!userStatus);
    return () => {};
  }, [userData, userStatus]);

in this scenario it will be a never ending loop

this works for me

    const dispatch = useDispatch()

    useEffect(() => {
        async function fetch() {
            try {
                const { data } = await getDiscussionByTitle(params.title)
                setDiscussion(data)
            } catch (error) {
                dispatch(addRequestError({ message: error.response.data }))
            }
        }

        fetch()
    }, [params.title, dispatch])

This is my solution for now:

const mounted = () => {
  dispatch(something());
}

useEffect(mounted, []);

Thank you @ra30r react/issues/15865#issuecomment-651254164

Solution -->just add dispatch in last for dependency React Hook useEffect has a missing dependency: ‘dispatch’.

  useEffect(() => {
    axios.get('http://localhost:8000/api/future-registrations')
      .then((result) => {
        dispatch(initRegistrationsAction(result.data));
      });

** }, [dispatch]);**

I’m presuming that your variable names are mixed up, and that setData() is actually setStateData(). If so, the reason that you don’t need to put useState setters in the dependency is array is because

you are right, it should be setStateData()

If by chance your setData is actually an unstable function (passed down in props?), that could cause an infinite loop.

And yes it’s an function exported and passed down by a context…

const [stateData, setStateData] = useState()

const setStateData = (key: string, value: any) => {
    setState((prev) => {
        return {
            ...prev,
            [key]: value,
        };
    });
};

....

return (
      <StateContext.Provider
          value={{
              ...state,
            setStateData
          }}
      >
          {children}
      </StateContext.Provider>
  );
};

export const useStateContext = () => React.useContext(StateContext);

Then used like this.

const [id, setId] = useState(null)
const {stateData, setStateData} = useStateContext()

useEffect(() => {
  const fetchData = async () => {
      const data = fetch(`/data/${id}`);
  
      if (data) {
          setData(data);
      }
  };
  
  if (id) { // only fetch data if id is set
      fetchData();
  }
}, [id, setData]); // No warning but results in a infinity loop

So it seems to be me being dumb and using it wrong.

Not dumb. I also find hook dependencies to be rather confusing, and often screw up my code without realizing it.

In your case, you can probably use useCallback() to make your function stable:

const setStateData = (key: string, value: any) => {
  setState(...)
}

->

const setStateData = useCallback((key: string, value: any) => {
  setState(...)
}, []) // shouldn't need `setState` as a dep

@spmsupun You can use the callback-style of useState to avoid introducing userStatus variable in the dependencies array of useEffect.

 useEffect(() => {
    setUserStatus(prevState => !prevState);
    return () => {};
  }, [userData]);

And this is so called functional updates.