restrict useEffect execution to only one dependency - reactjs

I´m playing with useEffect and i only need to "listen" to changes in one state but use another one without including it.
const [users, setUsers] = useState(["Mike"]);
const [message, setMessage] = useState();
useEffect(() => {
users.length > 0 && setMessage("we have users"!)
}, [users])
setMessage("hello!") // If I add this dependency to the previous useEffect it will trigger. but I will only need it to trigger on users change
setUsers(["john", "mary"])
doing this React will complain about the setMessage dependency
what´s the correct use of useState if I only one to listen to one state?
I ended up using. not sure if is the best approach. I´m just sharing what I tried :)
useEffect(() => {
users.length > 0 && functionIWantToExecuteOnceWithoutDependencies()
}, [users])

The eslint is giving warning specifying that you have missed adding setMessage as a dependency. But in your case, it seems the setMessage is a state setter and won't be changed thought your render cycle. Hence you can either ignore the eslint warning or disable the warning.
You can disable the warning like this:
useEffect(
() => {
users.length > 0 && setMessage("we have users"!)
// eslint-disable-next-line react-hooks/exhaustive-deps
[users],
);

Just add setMessage as a dependency. The react hook linter sees it as a called function, but since setMessage is a function created from the useState hook it is a stable reference so I won't cause any effect triggering.
You can disable lint rules per line if you want to, but that is generally not recommended. A scenario can arise where you update the effect, but because the dependency array linting is disabled you may introduce logical bugs.

Related

are missing dependencies in useEffect *always* mean bad design?

I have this piece of code in my react component:
const [editOrderComponentKey, setEditOrderComponentKey] = useState(0)
// clear/reload EditOrderPanel on a freshly loaded order
useEffect(() => {
setEditOrderComponentKey(editOrderComponentKey + 1)
/* eslint-disable-next-line react-hooks/exhaustive-deps */
}, [singleOrder])
the purpose of the code above, is to change the editOrderComponentKey when singleOrder has changed. I cannot put the editOrderComponentKey in the dependencies list, as the linter asks, because it will cause an infinite rendering, easy to see why.
anyway, this effect comes handy because few lines below I have the following code:
<EditOrderPanel orderId={orderId} key={`editOrder${editOrderComponentKey}`}/>
I want to invalidate EditOrderPanel if and only if singleOrder changes, so that EditOrderPanel would be re-mounted, and EditOrderPanel's local state would get its defaults anew (useState default).
is there a way to design the code such that no linter disablement would be required on that line, and still being able to use uncontrolled components?
In this case you can use the callback version to get the previous value.
const [editOrderComponentKey, setEditOrderComponentKey] = useState(0)
useEffect(() => {
setEditOrderComponentKey(prev => prev + 1)
}, [singleOrder, setEditOrderComponentKey])

How to set useEffect dependency when the calculation that uses stateB should be triggered by the change of stateA

function Example() {
const [stateA, stateA] = useState(0);
const [stateB, stateB] = useState(0);
useEffect(() => {
// this calculation should only be trigged by the change of stateA
// but it somehow uses the stateB for calculation
const calculation = () => {
console.log(stateB);
};
calculation();
}, [stateA]);
return null;
}
According to the guide from https://overreacted.io/a-complete-guide-to-useeffect/, I am wondering what is the correct way to set dependency in this case
If you're using the result of the calculation in your component, this sounds like a good use case for the useCallback hook.
const someResult = useCallback(() => {
return doSomething(stateB);
}, [stateA])
If you want to just run some random effect, then useEffect is correct. You just need to specify stateA in the dependency array.
useEffect(() => {
doSomething(stateB);
}, [stateA])
If you're using the exhaustive-deps linting rule, you'll get a waring in both cases. This is because your memoized calculation is relying on stateB but will not be recomputed when stateB updates, meaning your calculations could be stale. You have a couple options:
Add stateB to the dependency array (your calculation will then happen when stateA or stateB are updated.
Ignore the linting rule. You can suppress the warning by placing this comment on the line before your dependency array: // eslint-disable-next-line react-hooks/exhaustive-deps
Conventional wisdom is that suppressing this warning will come back to bite you, so it might be worth trying to figure out if you can accomplish what you need without this stale computation.

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.

setState in React's useEffect dependency array

What's the idea behind defining the setState values inside useEffect's dependency array?
const [someState, setSomeState] = useState();
...
useEffect(() => {
// Do something
setSomeState('some value');
}, [setSomeState]);
React Hook useEffect has a missing dependency: 'setSomeState'.
Either include it or remove the dependency array.
eslint(react-hooks/exhaustive-deps)
Not exactly sure if this example would cause eslint to ask to define setSomeState in the dependency array, but I have seen this kind of moments when writing with useEffect. What is React listening to in this case? My understanding is that useEffect listens for changes in the values in the dependency array, but would setSomeState ever change? What am I missing here?
In this case, no, the useState setter function will never change, but useEffect doesn't know it is a function from another hook, it just sees a function.
Consider this use case:
const MyComponent = ({ onUpdate }) => {
const [state, setState] = useState();
useEffect(() => {
// state updated, call onUpdate callback with state
onUpdate(state);
}, [onUpdate, state]);
return ...
}
Now parent component can, given
<MyComponent onUpdate={onUpdateHandler} />
define onUpdateHandler as
let onUpdateHandler = console.log;
then later change it while still mounted
onUpdateHandler = state => dispatch({ type: 'SAVE_STATE', state });
By placing the callback in the dependency array the effect hook see the value of onUpdate changed and trigger the effect again. If it didn't include the callback then it would continue to console.log state updates instead of now handling it differently.
It is more likely for values to change than it is for functions to change, but function values can change. You likely have an eslinter that is recommending you add all the variables used within the hook to the dependency array, and this will in most cases be the correct thing to do. A few exceptions are for example, when you want an effect to compute ONLY when the component mounts by specifying an empty array, or not including some variables that are functions that you KNOW will never change during the life of the component, and in these cases you can use the eslint-disable-next-line react-hooks/exhaustive-deps override to unflag it as a warning. Caution though, as this will disable the linting checks so it is possible for bugs to creep in if not paying attention.

useEffect dependency array and ESLint exhaustive-deps rule

I have a component that looks like this:
const MyComponent = props => {
const { checked, onChange, id } = props;
const [isChecked, setChecked] = useState(false);
useEffect(() => {
onChange && onChange({ isChecked: !!checked, id });
setChecked(checked);
}, [checked]);
const childProps = {
id,
isChecked
};
return <ChildComponent {...childProps} />;
};
The exhaustive-deps lint rule isn't happy:
React Hook useEffect has missing dependencies: id and onChange.
Either include them or remove the dependency array.
(react-hooks/exhaustive-deps)eslint
I know that id and onChange are not going to change, so adding them to the dependency array seems unnecessary. But the rule isn't a warning, it's a clear instruction to do something.
Is the ESLint rule:
1) Over-cautious and a bit dumb in this instance, so safe to ignore?
2) Highlighting best practice - i.e. to minimise unexpected bugs that might occur in future if, for instance, changes in parent components mean that id will change at some point in future?
3) Showing an actual/possible problem with the code as it currently is?
Old Answer
Actually the rule is very straightforward: Either pass an array containing all dependencies, or don't pass anything. So I guess the rule isn't dumb, it just doesn't know if the dependencies are going to change or not. So yes, if you are passing an array of dependencies it should contain ALL dependencies, including those you know for a fact that will not change. Something like this will throw a warning:
useEffect(() => dispatch({ someAction }), [])
And to fix this you should pass dispatch as a dependency, even though it will never change:
useEffect(() => dispatch({ someAction }), [dispatch])
Don't disable the exhaustive deps rule, as mentioned here
UPDATE 05/04/2021
As addressed here. This is no longer necessary since eslint pull #1950.
Now referential types with stable signature such as those provenients from useState or useDispatch can safely be used inside an effect without triggering exhaustive-deps even when coming from props
The way to look at it is every render has its own effect. If the effect will be the same with a particular set of values, then we can tell React about those values in the dependencies array. Ideally, a component with the same state and props, will always have the same output (rendered component + effect) after its render and effect is done. This is what makes it more resilient to bugs.
The point of the rule is that, if the deps DO change, the effect SHOULD run again, because it is now a different effect.
These 3 links also give more insights about this:
https://github.com/facebook/react/issues/14920#issuecomment-471070149
https://overreacted.io/a-complete-guide-to-useeffect/
https://overreacted.io/writing-resilient-components/

Resources