So instead of "do something when this action/event happens" we get "do something when this data changes". Which ends up being abused a lot, as developers who aren't familiar with this mental model (which is most of them), tend to lump up every action into a data change.
So instead of something like (contrived example):
openAlert() {
alert("I'm an alert");
}
return <button onClick={openAlert()} />
people do: const [alertOpen,setAlertOpen] = useState(false);
useEffect(()=>{
if (alertOpen){
alert("I'm an alert");
}
return ()=>setAlertOpen(false); //if they even remember to unregister
},[alertOpen])
return <button onClick={()=setAlertOpen(true)} />
Beyond that there are multiple other problems:- the fact that useEffect (and hooks in general) is order dependent and can't be put inside branching (so I can't start an affect after an event happens).
- the fact that inside useEffect you read the data of the previous render is very unintuitive (I.E. your data is stale).
- the need to always put all your dependencies that are used inside useEffect in the dependency array explicitly - which can be even harder when dependencies are dynamic - is also a problem for many.
There's probably a few more, but that off the top of my mind.
The fact that there needs to be several eslint rules in order for people to not footgun themselves constantly is quite a red flag on it's own.
And are you sure the data in useEffect is stale? I don't think it is, the documentation[0] seems to suggest that it uses the current closure.
The rest I agree with.
[0] https://reactjs.org/docs/hooks-effect.html#example-using-hoo...
See "You don’t need Effects to handle user events".