Warning 'React hook useEffect has a missing dependency' - reactjs

In a React app I get this warning on a few components in the useEffect function. I have seen other SO questions but still cant see a fix.
React Hook useEffect has a missing dependency: 'digiti' and 'time'. Either include it or remove the dependency array
const GenerateNumber = (props) => {
const [number, setNumber] = useState(0);
// add side effect to component
useEffect(() => {
const interval = setInterval(
() => setNumber(Math.floor(Math.random() * 9 + 1)),
50
);
setTimeout(() => { clearInterval(interval); setNumber(props.digiti); }, props.times * 100);
}, []);
return (
<span className='digit'>{number}</span>
);
}

This is something the react hooks exhaustive deps explain. In general, your dependency array should include all values used in the dependency array, however when you do this with something like setNumber, your useEffect hook will run infinitely as each change of setNumber triggers a new render (and each new render triggers setNumber, see the problem there?).
Your actual error, with the prop values of both digiti and times aim at you adding these two values to the dependency array, which would case the useEffect hook to run again every time these props change. It is up to you if this is intended behavior.
What is actually documented in the dependency array documentation is that it is intended behavior to leave the array empty to have the useEffect hook run exactly once.

Related

Difference between useEffect and useMemo

I'm trying to wrap my head around what exactly the useMemo() React hook is.
Suppose I have this useMemo:
const Foo = () => {
const message = useMemo(() => {
return readMessageFromDisk()
}, [])
return <p>{message}</p>
}
Isn't that exactly the same as using a useState() and useEffect() hook?
const MyComponent = () => {
const [message, setMessage] = useState('')
useEffect(() => {
setMessage(readMessageFromDisk())
}, [])
return <p>{message}</p>
}
In both cases the useMemo and useEffect will only call if a dependency changes.
And if both snippes are the same: what is the benefit of useMemo?
Is it purely a shorthand notation for the above useEffect snippet. Or is there some other benefit of using useMemo?
useEffect is used to run the block of code if the dependencies change. In general you will use this to run specific code on the component mounting and/or every time you're monitoring a specific prop or state change.
useMemo is used to calculate and return a value if the dependencies change. You will want to use this to memoize a complex calculation, e.g. filtering an array. This way you can choose to only calculate the filtered array every time the array changes (by putting it in the dependency array) instead of every render.
useMemo does the same thing as your useEffect example above except it has some additional performance benefits in the way it runs under the hood.

How do you fix 'React Hook useEffect has a missing dependency:' warnings?

I get two warnings from these two useEffects. I am learning React.js and this is my first time dealing with this issue.
const fetchShippingCountries = async (checkoutTokenId) => {
const { countries } = await commerce.services.localeListShippingCountries(checkoutTokenId)
useEffect(() => {
fetchShippingCountries(checkoutToken.id);
}, []);
const fetchShippingOptions = async (checkoutTokenId, country, region = null) => {
const options = await commerce.checkout.getShippingOptions(checkoutTokenId, { country, region });
setShippingOptions(options);
setShippingOption(options[0].id);
}
useEffect(() => {
if(shippingSubdivision) fetchShippingOptions(checkoutToken.id, shippingCountry, shippingSubdivision);
}, [shippingSubdivision]);
React Hook useEffect has a missing dependency: 'checkoutToken.id'.
Either include it or remove the dependency array
React Hook useEffect has missing dependencies: 'checkoutToken.id' and 'shippingCountry'. Either include them or remove the dependency array
React is asking you to pass the 'checkoutToken.id' and 'shippingCountry' variables into the useEffect dependency array. useEffect is always called whenever the variables within dependency array change. Since you're using these two variables within useEffect, React is warning you that a change in them will not trigger the useEffect.
It's a part of the best practices to add all the variables being used by the useEffect function into the dependency array. This is to make sure you are keeping complete control over the changes within your React app.

What should be the dependencies of useEffect Hook?

As it's said, the useEffect hook is the place where we do the side-effects related part. I'm having a confusion of what dependencies should be passed in the dependency array of useEffect hook?
The React documentation says
If you use this optimization, make sure the array includes all values from the component scope (such as props and state) that change over time and that are used by the effect. Otherwise, your code will reference stale values from previous renders.
Consider an example:
export default function App() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
console.log("component mounted");
}, []);
const update = () => {
for(let i=0; i<5;i++){
setCount(count+i)
}
};
return (
<div>
{console.log(count)}
{count}
<button type="button" onClick={update}>
Add
</button>
</div>
);
}
Going with the above statement, we should pass the count variable as a dependency to useEffect, it will make the useEffect re run.
But in the above example, not passing the count dependency doesn't create any problem with the UI. There is not stale value. The variable updates as expected, UI re-renders with exact value, logs the current value.
So my question is, why should we pass the count variable as dependency to useEffect. The UI is working correctly?
UPDATE
I know the useEffect callback is triggered everytime when the value in dependency array changes. My question is what should go in to the dependency array? Do we really need to pass state variables?
Thanks in advance.
To demonstrate and understand the issue print the count variable inside
React.useEffect(() => {
console.log("component updated ", count);
}, []); // try to add it here
click again the button and see how it logs to the console
EDIT:
If you want to get notified by your IDE that you are missing dependencies then use this plugin
https://reactjs.org/docs/hooks-rules.html#eslint-plugin
Keep in mind that it will still complain if you want to simulate onMount
What should go into the dependency array?
Those things (props/state) that change over time and that are used by the effect.
In the example, the UI works correctly because setState re-renders the component.
But if we do some side-effect like calling an alert on change of count, we have to pass count to the dependency array. This will make sure the callback is called everytime the dependency (count) in our case, changes.
React.useEffect(() => {
alert(`Count ${count}`); // call everytime count changes
}, [count]); // makes the callback run everytime, the count updates.
If property from second argument in useEffect change - then component will rerender.
If you pass the count -> then component rerender after count change - one time.
React.useEffect(() => {
console.log("component mounted");
}, [count]);
Quick answer:
Pass everytime you need to trigger refreshing component.
useEffect will get called in an infinite loop unless you give it dependencies.
React.useEffect(() => {
console.log("Looped endlessly");
}); // dependencies parameter missing
Adding an empty dependency list will cause it to get called just once on component did mount
React.useEffect(() => {
console.log("Called once on component mount");
}, []); // empty dependency list
Add a state to the dependency list to get called when state gets updated
React.useEffect(() => {
console.log("Called once on component mount and whenever count changes");
console.log("Count: " + count);
}, [count]); // count as a dependency

Missing dependency: 'dispatch' in React

In my react/redux app, i'm using dispatch to call the action that retrieve data from state in redux each time the component is mounted. The problem is happening on useState My way does not work
Below is the error I'm getting:
React Hook useEffect has a missing dependency: 'dispatch'. Either include it or remove the dependency array. Outer scope values like 'getInvoiceData' aren't valid dependencies because mutating them doesn't re-render the component react-hooks/exhaustive-deps
Here is my code:
const TableSection = () => {
const invoiceData = useSelector((state => state.tables.invoiceData));
const dispatch = useDispatch()
useEffect(() => {
dispatch(getInvoiceData());
}, [getInvoiceData]);
(...)
export default TableSection;
You need to add dispatch function to dep array:
const TableSection = () => {
const dispatch = useDispatch()
useEffect(() => {
dispatch(getInvoiceData());
}, [dispatch]);
Its safe to add it to dep array because its identity is stable across renders, see docs.
Note: like in React's useReducer, the returned dispatch function identity is stable and won't change on re-renders (unless you change the store being passed to the , which would be extremely unusual).
This is not an error, its just a warning.
You can fix this by adding dispatch in the dependency array.
useEffect(() => {
dispatch(getInvoiceData());
}, [dispatch]);
second part of the warning message states, Outer scope values like 'getInvoiceData' aren't valid dependencies because mutating them doesn't re-render the component react-hooks/exhaustive-deps, you also need to remove getInvoiceData function from the dependency array of useEffect hook.
Anything from the scope of the functional component, that participates in react's data flow, that you use inside the callback function of useEffect should be added in the dependency array of the useEffect hook.
Although, in your case, it is safe to omit dispatch function from the dependency array because its guaranteed to never change but still it won't do any harm if you add it as a dependency.

Why React expects me to add setters as useEffects dependencies

I am trying to use an async function inside a useEffect callback.
There is a behavior i don't understand (in my case related to the throttle function from lodash).
I don't get what is happing, and how to solve it, here is a sample of the code:
import throttle from 'lodash/throttle';
const myRequestWrapped = throttle(myRequest, 300);
const [name, setName] = useState('');
useEffect(() => {
myRequest(name) // No warning
myRequestWrapped(name); // React Hook useEffect has a missing dependency: 'myRequestWrapped'. Either include it or remove the dependency array
}, [ name ]);
If i add myRequestWrapped as a dependency, i have an infinite loop (the effect is triggered continuously).
I guess the throttle method works with a timer and it returns a different result at every run so i can understand why the infinite loop.
But i really don't understand why React wants it as a dependency (especially that it works without adding it !).
What is the logic?
Why myRequestWrapped and not myRequest?
Should i ignore the warning or do you know a clean way to solve that?
Thanks.
Its not React that wants you to add myRequestWrapped as a dependency but its eslint.
Also you must note that ESLint isn't aware of the programmers intention so it just warns the user if there is a scope of error being made.
Hooks heavily rely on closures and sometimes its difficult to figure out bugs related to closures and that is why eslint prompts if there is a case of a fucntion of variabled used within useEffect that might reflect the updated values.
Of course the check isn't perfect and you could carefully decide whether you need to add a dependency to useEffect or not.
If you see that what you wrote is perfectly correct. You can disable the warning
useEffect(() => {
myRequest(name);
myRequestWrapped(name);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ name ]);
Also you must not that throttle function cannot be used within render of functional componentDirectly as it won't be effective if it sets state as the reference of it will change
The solution here is to use useCallback hook. Post that even if you add myRequestWrapped as a dependency to useEffect you won't be seeing an infinite loop since the function will only be created once as useCallback will memoize and return the same reference of the function on each render.
But again you must be careful about adding dependency to useCallback
import throttle from 'lodash/throttle';
const Comp = () => {
const myRequestWrapped = useCallback(throttle(myRequest, 300), []);
const [name, setName] = useState('');
useEffect(() => {
myRequest(name);
myRequestWrapped(name);
}, [ name ]);
...
}
Shubham is right: it's not REACT, but ESLint instead (or TSLint, depending on the Linter you are using).
If I may add, the reason why the Linter suggest you to add myRequestWrapped is because of how the closures work in JavaScript.
To let you understand, take this easier example (and I need this because I would need to know what it's inside myRequestWrapped:
const MyComp = props => {
const [count, setCount] = React.useState(0);
const handleEvent = (e) => {
console.log(count);
}
React.useEffect(() => {
document.body.addEventListener('click', handleEvent);
return () => { document.body.removeEventListener('click', handleEvent); }
}, []);
return <button onClick={e => setCount(s => s +1)}>Click me</button>
}
So, right now, when MyComp is mounted, the event listener is added to the document.body, and anytime the click event is triggered, you call handleEvent, which will log count.
But, since the event listener is added when the component is mounted, the variable count inside handleEvent is, and always will be, equal to 0: that is because the instance of handleEvent created when you added the event listener is just one, the one that you associated with the event listener.
Instead, if you would write the useEffect like this:
React.useEffect(() => {
document.body.addEventListener('click', handleEvent);
return () => { document.body.removeEventListener('click', handleEvent); }
}, [handleEevent]);
Anytime the handleEvent method is updated, also your event listener is updated with the one handleEvent, thus when clicking on the document.body you will always log the latest count value.

Resources