3

I am trying updating data in dispatch in useEffect but showing warning in console

React Hook useEffect has missing dependencies: 'dispatch', 'id', and 'state.selectedHotel'. Either include them or remove the dependency array  react-hooks/exhaustive-deps

code

import { GlobalContext } from "../../../context/globalContext";
const HotelDetail = () => {
    const [state, dispatch] = useContext(GlobalContext);
    const { id } = useParams();

   useEffect(() => {
       const hotelData = async () => {
          try {
              let response = await ServiceGetAllHotels();
              let hotel = response.hotels.filter(hotel => {
                    return hotel.hotelUserName === id;
              });
              dispatch({
                  type: "UPDATE",
                  payload: { selectedHotel: hotel[0] }
              });
          }catch(){}
       };
   }, [])
};

But warning message disappear when I add this (below code)

useEffect(() => {
.....
}, [dispatch, state.selectedHotel, id])

I dont understand why this error/warning , why error disappear when I add this ? Please help Can I go with this code?

Hunter
  • 1,515
  • 4
  • 15
  • 25
  • https://reactjs.org/docs/hooks-reference.html#conditionally-firing-an-effect – jonrsharpe Jun 24 '21 at 07:49
  • 2
    I'm confused too. The error/warning tells you what to do to fix the issue, you do that and then you are wondering why it fixed the issue? What is it that you really want to know? – Felix Kling Jun 24 '21 at 07:50
  • DO you think that my code is correct way? @FelixKling , I was experimenting to fix this issue – Hunter Jun 24 '21 at 07:52
  • Does this answer your question? [How to fix missing dependency warning when using useEffect React Hook?](https://stackoverflow.com/questions/55840294/how-to-fix-missing-dependency-warning-when-using-useeffect-react-hook) – Ethan Vu Jun 24 '21 at 07:52
  • The error says to add those values to the dependency list and that's exactly what you did. – Felix Kling Jun 24 '21 at 07:54
  • This is a weird eslint rule, which doesn't really make sense when I want to have `useEffect` to fire onMount. I always just use `// eslint-disable-next-line react-hooks/exhaustive-deps,` on occasions like this. Not the cleanest solution though – SakisTsalk Jun 24 '21 at 08:01

2 Answers2

5

Its not an error but a warning that can save you from bugs because of useEffect hook not running when it was supposed to.

useEffect hook, by default, executes after:

  • the initial render
  • each time a component is re-rendered

Sometimes we don't want this default behavior; passing a second optional argument to useEffect hook changes the default execution behavior of useEffect hook. Second argument to useEffect hook is known as its dependency array that tells React when to execute the useEffect hook.

Run "useEffect" once, after the initial render

We can achieve this by passing an empty array as the second argument to the useEffect hook:

useEffect(() => {
  // code
}, []);

This effect will only execute once, similar to componentDidMount in class components.

Run "useEffect" everytime any of its dependency changes

When the code inside the useEffect depends on the state or a prop, you sometimes want useEffect to execute every time that piece of state or prop changes.

How can we tell React to run the effect every time a particular state or prop changes? By adding that state or prop in the dependency array of the useEffect hook.

Example:

Imagine a Post component that receives post id as a prop and it fetches the comments related to that post.

You might write the following code to fetch the comments:

useEffect(() => {
   
   fetch(`/${props.postId}`)
     .then(res => res.json())
     .then(comments => setComments(comments))
     .catch(...)

}, []);

Problem with the above code:

When the Post component is rendered for the first time, useEffect hook will execute, fetching the comments using the id of the post passed in as the argument.

But what if the post id changes or the post id is not available during the first render of the Post component?

If post id prop changes, Post component will re-render BUT the post comments will not be fetched because useEffect hook will only execute once, after the initial render.

How can you solve this problem?

By adding post id prop in the dependency array of the useEffect hook.

useEffect(() => {
   
   fetch(`/${props.postId}`)
     .then(res => res.json())
     .then(comments => setComments(comments))
     .catch(...)

}, [props.postId]);

Now every time post id changes, useEffect will be executed, fetching the comments related to the post.


This is the kind of problem you can run into by missing the dependencies of the useEffect hook and React is warning you about it.

You should not omit any dependencies of the useEffect hook or other hooks like: useMemo or useCallback. Not omitting them will save you from such warnings from React but more importantly, it will save you from bugs.

Infinite loop of state update and re-render

One thing to keep in mind when adding dependencies in the dependency array of the useEffect is that if your are not careful, your code can get stuck in an infinite cycle of:

useEffect --> state update ---> re-render --> useEffect ....

Consider the following example:

useEffect(() => {
  const newState = state.map(...);
  setState(data);
}, [state, setState]);

In the above example, if we remove the state from the dependency array, we will get a warning about missing dependencies and if we add state in the array, we will get an infinite cycle of state update and re-render.

What can we do?

One way is to skip the state as a dependency of the useState hook and disable the warning using the following:

// eslint-disable-next-line react-hooks/exhaustive-deps

Above solution will work but it's not ideal.

Ideal solution is to change your code in such a way that allows you to remove the dependency that is causing the problem. In this case, we can simply use the functional form of the setState which takes a callback function as shown below:

useEffect(() => {
  setState(currState => currState.map(...));
}, [setState]);

Now we don't need to add state in the dependency array - problem solved!

Summary

  • Don't omit the dependencies of the useEffect hook
  • Be mindful of the infinite cycle of state update and re-render. If you face this problem, try to change your code in such a way that you can safely remove the dependency that is causing the infinite cycle
Yousaf
  • 27,861
  • 6
  • 44
  • 69
  • Very thanks for detailed answer , So I can go with this code [dispatch, state.selectedHotel, id]) @Yousaf – Hunter Jun 24 '21 at 08:12
  • If the `useEffect` doesn't updates the `state.selectedHotel`, then yes. – Yousaf Jun 24 '21 at 09:33
  • But that's confusing as setState is not a value that will change, what's the significance of adding it to array? – Abhishek Thapliyal Apr 04 '22 at 05:19
  • @AbhishekThapliyal It doesn't makes any difference because, as you said, its value doesn't change. Having said that, I personally add it in the dependency array as a personal preference. – Yousaf Apr 04 '22 at 05:35
  • I was looking for a reason though, but I got similar example i.e. dispatch usage in array here : https://react-redux.js.org/api/hooks – Abhishek Thapliyal Apr 04 '22 at 05:56
  • I do it for making it clear what `useEffect` depends on and also for the sake of being consistent across the codebase. If it's a dependency, I add it in the dependency array, regardless of whether its value changes or not. – Yousaf Apr 04 '22 at 05:58
1

The useEffect hook accepts two arguments. The first one is a classic callback and the second one is an array of so called "dependencies".

The hook is designed to execute the callback immediately after component has been mount (after elements have been successfully added to the real DOM and references are available) and then on every render if at least one of the values in the dependencies array has changed.

So, if you pass an empty array, your callback will be executed only once during the full lifecycle of your component.

It makes sense if you think about it from a memory point of view. Each time that the component function is executed, a new callback is created storing references to the current execution context variables. If those variables change and a new callback is not created, then the old callback would still use the old values.

This is why "missing dependencies" is marked as a warning (not an error), code could perfectly work with missing dependencies, sometimes it could be also intentional. Even if you can always add all dependencies and then perform internal checks. It is a good practice to pass all your dependencies so your callback is always up to date.

Ernesto Stifano
  • 3,027
  • 1
  • 10
  • 20