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

Created on 12 Jun 2019  ·  69Comments  ·  Source: facebook/react

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>
  );
}

Most helpful comment

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

All 69 comments

This should answer your question:

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

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.

My question is :React Hook cannot call a function in the useEffect and function component to setState operation?

Leaving open so we can respond to second question.

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.

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));
      });
  }, []);

Same issue here. Strange rule.

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?

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

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

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

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

@shkyung In my case, initFetch will not change cause (store.)dispatch doesn't either

Maybe it can be like this

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

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

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.

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.

React Hook useEffect has a missing dependency: 'dispatch'.

const [state, dispatch] = useReducer(reducer, initialState);
const { count, step } = state;

useEffect(() => {
  const id = setInterval(() => {
    dispatch({ type: 'tick' }); // Instead of setCount(c => c + step);
  }, 1000);
  return () => clearInterval(id);
}, [dispatch]);

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 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.

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.

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.

Id like to put in my two cents here. In particular with using Axios to cancel a network request. Will create a separate issue if required but felt this issue directly affected myself. So my useApi hook is as follows -

import axios, { AxiosInstance } from "axios";
import axiosRetry from "axios-retry";
import { FetchEnv } from "../../utilities";
import { useStorage } from "../useStorage";

export const useApi = ({ isAuth = false, retries = 3 } = {}) => {
  const cancelToken = axios.CancelToken.source();
  const { getItem } = useStorage();

  const getBaseUrl = () => {
    return FetchEnv({
      defaultValue: "http://api.example.com",
      key: "API_URI"
    });
  };

  const authorizedClient = (client: AxiosInstance) => {
    const session = getItem("SESSION", "sessionStorage");
    const hasAccessToken = Boolean(session.tokens.accessToken);

    if (isAuth && !hasAccessToken) {
      console.error("No access token found to initiate authorized request.");
      return client;
    } else {
      client.defaults.headers[
        "Authorization"
      ] = `Bearer ${session.tokens.accessToken}`;
      return client;
    }
  };

  const buildFetch = (): AxiosInstance => {
    const client = axios.create({
      baseURL: getBaseUrl(),
      cancelToken: cancelToken.token
    });
    axiosRetry(client, { retries });
    return isAuth ? authorizedClient(client) : client;
  };

  return {
    cancelRequest: cancelToken.cancel,
    fetch: buildFetch()
  };
};

To use this hook within a component, simply define it as below then it can be used to make both authenticated and unauthenticated calls either on mount, within a useEffect, as part of an event handler, etc. The cancelRequest is the unique cancel token generated that is paired with the "fetch" axios instance relative to this component.

const { cancelRequest, fetch } = useApi();

If for any reason this component dismounts, the following is called:

  useEffect(() => {
    return () => {
      cancelRequest("Auth network request cancelled");
    };
  }, []);

Now it works perfectly at present. However, the warning, when applied (putting cancelRequest as a useEffect dependency) immediately CANCELS the network request when the fetch method is called. Any advice would be greatly appreciated however it would appear the only solution at present would be to ts-lint disable this moving forward...

I think the issue here may be the way we are thinking about hooks. @luojinghui's comment suggests that there is more to this than meets the eye. Perhaps we need to think differently? I have been able to remove this problem in one of my hooks with some refactoring. The code is clearer as a result.

It led me to wonder if, in @Stoner609's example just above this, the timing code should be in a useCallback hook? It is still odd having a function (which should be static) as a dependancy though.

I think this case should be some sort of exception

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]);

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

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

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

As much as I love this witty reply this usually is what happens in my code.
I don't want to have all those pesky warning about dispatch going missing.

I'd love to know exactly what and why happens in that array of mystery :)

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.

Really need a fix for this. It keeps giving me errors and no ideas how to solve it if you want to fire a function inside useEffect() just once.

Facing the same error -

React Hook useEffect has a missing dependency: 'props'. Either include it or remove the dependency array. However, 'props' will change when any prop changes, so the preferred fix is to destructure the 'props' object outside of the useEffect call and refer to those specific props inside useEffect react-hooks/exhaustive-deps

Code -

  useEffect(() => {
    props.dispatch(searchMediaAction("rain"));
  }, []);

@luojinghui : Let me know if you resolved the lint error

same error

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

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.

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.

Why is this closed?

Any solutions?

@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.

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

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

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.

@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.

For me, the error appeared in the following situation:

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

I corrected it like this:

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

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

my issue was different than others in this thread, so i'll share it in case any other poor soul goes down the rabbit hole i just have been down for 1 1/2 hours

function hydrate( _state, _errors=false) {
    console.log('hydrate()');
    setState({...state,..._state});
    if(_errors){
        setErrors({...errors,..._errors});
    }
}

the user is expected to use this with useEffect() in order to hydrate their state like so:

useEffect(()=>{
    hydrate({
        name:'Garrett',
        email:'[email protected]',
        newsletter: 'yes'
    });
},[hydrate]); //unfortunately, this causes an infinite render, because hydrate will always change

so to solve this, i just changed my hydrate function to be wrapped in useCallback(), now it is not mutated every render ( i guess ), and supposedly this is better for performance, which if that turns to be true i will implement this for all of my helper function returned from my custom hook.

const hydrate = useCallback(( _state, _errors=false )=> {
    console.log('hydrate()');
    setState({...state,..._state});
    if(_errors){
        setErrors({...errors,..._errors});
    }
},[]);

can anyone confirm if this is true, useCallback is preventing hydrate from being mutated in the render, and thus it is probably better to wrap all such functions in useCallback for better performance?

redux can guarantee that the dispatch function will not change between renders.

Are you sure about that?
Link to the docs where it says so please.

Check https://overreacted.io/a-complete-guide-to-useeffect/#decoupling-updates-from-actions for more details or you may check useEffect source code for implementation details.

Here I just reference what Dan said:

The answer is that React guarantees the dispatch function to be constant throughout the component lifetime. So the example above doesn’t ever need to resubscribe the interval.

guarantees the dispatch function to be constant

Cool, thanks!

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.

Well, not much else to say, aside the fact that the thread is closed smh.

Faces the same issue!

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...

@ra30r nice workaround but wondering if it can create side effects ? :thinking:

Still nothing? 😔

I had a case where I learned to trigger form validation errors from a course on Udemy.

This triggered the missing dependency "dispatch", but still worked:

  useEffect(() => {
    if (state.business_name.value) {
      const delay = setTimeout(() => dispatch({ type: "businessNameAfterDelay" }), 1000)
      return () => clearTimeout(delay)
    }
  }, [state.business_name.value])

Using @ra30r 's example, I changed it to this to clear the error:

  const businessNameAfterDelay = function () {
    if (state.business_name.value) {
      const delay = setTimeout(() => dispatch({ type: "businessNameAfterDelay" }), 1000)
      return () => clearTimeout(delay)
    }
  }

  useEffect(businessNameAfterDelay, [state.business_name.value])

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?

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

hope this helps!!

Yes, that workaround works, @kennylbj said here some time ago.
Why the issue is closed? There are memes here also about it... xd

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

@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.

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]);**

@kennylbj This is good when you deal with one state, my actual scenario is with props. I am sending a props from parent and it change twice so usereffect calls twice but I am running a listener inside it and it will listen twice.

@spmsupun But I think it's quite normal to unregister the previous listener and register a new listener whenever the variables in dependencies array changed(eg. when the userId in props changed, the listener also needed to be changed). And this behavior is what react expects to have.

If you still don't want to make the listener called twice, I think you should take more care of the props passed to it and make sure it will not be changed during renders.

@kennylbj well I do unregister but still it not a good practise
image
Guess I have to handle this from listener class,

Seems like your listener is called twice even if the socket id are the same.

I don't think this will happen if the following situation meets:

ts // call useEffect if and only if sockeId changed. useEffect(() => { const unregister = register(socketId); return () => unregister(socketId); }, [socketId])

Are there any other variables in the dependencies array changed during different renders and cause this behavior?

Yeah 3 dependencies, but I figure it out some different way

ぴえん

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

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

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

wow,非常感谢你🙏

🚀 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])

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

🚀 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])

nice work.

I don't think the react docs quite cover @luojinghui 's use case, that is also the same that I have:

We have multiple ways of calling a given function, ie: setCenterPosition(). It could be called by watching for state change (hooked up via useEffect), a click handler (called directly), etc.

The react docs suggest

This is why usually you’ll want to declare functions needed by an effect inside of it

But usually in real-world examples, we need to reuse functions like we normally would with instance methods on a class component (this.setCenterPosition(...)), and I'm starting to wonder if hooks are actually good for feature-full, complex components.

I'm not actually sure, but maybe the solution is useCallback

const setCenterPosition = useCallback(() => {
  ...
}, [Stage, elePositionArr, setElePositionArr]);

useEffect() => {
  ...
}, [stageRef, Stage, currentIndex, setCenterPosition]);

...but that's kinda gross to me, and I'm hoping there's a cleaner way. Maybe @gaearon can confirm?

facing the same issue i can disable the warning bu using this line at the end of useEffect // eslint-disable-next-line react-hooks/exhaustive-deps but i don't want to use it

Here's My code

`const [items, setItems] = useState([
{
name: "test 1",
id: 1
},
{
name: "test 2",
id: 2
},
{
name: "test 3",
id: 3
},
{
name: "test 4",
id: 4
},
{
name: "test 5",
id: 5
}
]);

useEffect(() => {
const intervalId = setInterval(() => {
setItems(shuffleArray(items));
}, 1000);
return () => {
clearInterval(intervalId);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

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).

This is my solution for now:

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

useEffect(mounted, []);

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

Was this page helpful?
0 / 5 - 0 ratings