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

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

Related

Warning 'React hook useEffect has a missing dependency'

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.

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.

restrict useEffect execution to only one dependency

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.

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