There are some weird edge cases with useEffect, no question.
I don’t think they’re bad enough to want to go back to using classes... but I think the OPs post was a bit more specific than you’re giving them credit for, and they stuff like useAsyncEffect exists because it is a common pain point.
For example, this useRef / useEffect combo mystifies me even now: https://stackblitz.com/edit/react-ts-zhvuha
The behaviour is weird sometimes; just because you can use the trivial use case easily doesn’t mean you won’t stumble, and if you do, good luck trying to understand what went wrong.
> ...they're very simple to understand...
They’re not simple.
They just superficially appear simple, which is good enough most of the time.
I think it’s entirely fair to complain that’s not ideal... even if you still think there’s a major benefit to the trade off compared to classes (which I do think is true too).
...but I’m very sympathetic to people hitting edge cases.
On this particular point, the reasoning is that refs are meant for values that don't need to trigger a rerender[1] -- an escape hatch, rather than something you reach for by default.
In that sandbox, you can accomplish what you mean to accomplish by using state rather than a ref.
[1] https://github.com/facebook/react/issues/14387#issuecomment-...
I'm not sure if there is a less confusing way of doing it, but it's damn confusing.
This is simply a matter of reading the docs (or making the mistake until you learn). Not an inherently more complex characteristic than, say, Class component lifecycle methods, which also required understanding their rules and reading the docs.
Once you know refs updates won't re-render, you won't make this mistake.
I don't understand why "a thing you need to learn then you're good" is somehow inherently more complicated than ... other apis that you need to also learn.
> but most people think of refs as...
This sounds like Dan Abramov's comment that some of the struggles with hooks is from people who have experience with react without hooks, vs people coming new to the whole thing. So maybe it's about relaxing pre-conceived notions until experience takes over.
Also that the fix is "instead of using this one kind ref, use another kind of ref and put it in state"... I don't know, like I said I'm not sure if there is a better solution, but it still feels kind of unintuitive and complicated.
Now that I'm thinking about it... what is the reason that DOM refs and other refs need to be handled by the same concept? Every time I make a DOM ref, I'm doing something with it in a hook like useEffect. Why make me jump through hoops to re-run the hook if the DOM ref changes?
(I recognize there are probably good answers to those questions, the React folks are great, I just don't know the answers! Is it just to avoid introducing one more "type of thing", and instead making refs and DOM refs the same "thing"?)
Ref's are basically a 1 to 1 replacement for instance fields for mutable data (that doesn't cause rerenders).
https://reactjs.org/docs/hooks-reference.html#useref
There's always need for escape hatches and maybe I'm missing your use case, but in the context of a discussion about how hooks are more complicated than classes, what were you doing before with refs to solve your "re-render" on change scenario?
Your example was likely contrived, but modifying innerHTML should be replaced by putting whatever in state and simply rendering it. And use state for dependencies where you want to re-run on change. Refs are just another way to keep state but not have it affect render cycle.
If you're not aware of that, it is very tempting to use `useRef`, which is what I have often seen. Before hooks, we did not have the temptingly-named footgun `useRef` for this scenario (although we did have other footguns for other scenarios, and overall I love hooks).
It’s harder to just use state for example, when you need a ref to a canvas to get the 2d context.
The classic X here, of course, is monads.
But as personal examples, it took me ages of headbanging to get the 'aha' moment for git, even though a bunch of people I know got it almost immediately.
In the case of hooks, once I built a mental model of how they were (theoretically) implemented, I went 'ooh' and found them obvious and relatively easy from then on. It looks to me like the author of this piece didn't have that luck, and so for him, they're not simple (although maybe later, post-aha-moment, they will be).
tl;dr: "They seem simple to understand once you understand them, but are mystifying up until that point" seems to me to be the most likely hypothesis here.
It more-or-less boils down to: 1. class-based components force your lifecycle logic to live in disparate locations. 2. Class-based hooks are obtuse with hidden gotchas whereas pure functions tell you exactly what they're doing. 3. A whole class of unergenomic solutions disappears when you use hooks.
Hooks have their share of weird issues; they explode if they're called in a different order/number from how they were called the first time the component rendered (making them not at all pure functions), and it's super easy to screw up the dependency array to useMemo/useCallback (so either your hook is useless and re-installs the event handler on every render, or you get stupid "the callback had a stale copy of the state" bugs not possible with class-components).
I've also seen my fair share of situations where what would have been a this.setState with a callback becomes a tangled mess of useStates and useEffects, or someone bends over backwards to try and avoid re-installing an event handler every time one of the props or state variables it uses changes.
I definitely get that there are some more complex cases where you have to jump through a couple of useRef hoops to do what you need. On balance though, I think the way the behaviour becomes declarative is worth the trade off.
And you already have to wonder if a function has state if you didn't write it. If you did, you should know.
You have more control too. Look at the parameters to useState to see what I mean.
The class API lifecycle methods are kind of a mess, and useEffect() seems like a more convenient abstraction. But I could imagine replacing the lifecycle methods with hooks that are added at object construction and get similar benefits. I wonder if the problem is not so much classes, but just the API that happened to evolve.
* Removed "pure" to make my main point clear.
But my point was functions are simpler in all those scenarios, pure or not. Edited my comment to reflect that.
Sure, if you define a function called useWhatever that doesn't actually call any hooks, that might be a pure function. But the restrictions described in https://reactjs.org/docs/hooks-rules.html are not restrictions that pure functions have.
“Just burn it to the ground and start over,” isn’t an inspiring endorsement of the methodology...
The only real dislike I have, is in how `useEffect` confuses the React lifecycle. It took me a little while to grok the lifecycle idea, but it was/is a useful way to conceptualise what happens to your components at runtime.
But now, you have to remap all that knowledge to the hooks workflow. It's not a huge challenge, but it's yet another overhead.
Indeed, starting out, our team ran into a number of issues with infinite rendering loops and the like. Took a fair amount of reading to discover where the pitfalls were.
See the following resources for more details:
- Mark Erikson - Reactathon 2019: The State of Redux (https://blog.isquaredsoftware.com/2019/03/presentation-state...)
- Mark Erikson: Redux - Not Dead Yet! (https://blog.isquaredsoftware.com/2018/03/redux-not-dead-yet...)
- Dave Ceddia: React Context API vs Redux (https://daveceddia.com/context-api-vs-redux/)
- Mike Green: You Might Not Need Redux (But You Can’t Replace It With Hooks) (https://www.simplethread.com/cant-replace-redux-with-hooks/)
- Sergey Ryzhov: From Redux to Hooks: A Case Study (https://staleclosures.dev/from-redux-to-hooks-case-study/)
- Eric Elliott: Do React Hooks Replace Redux? (https://medium.com/javascript-scene/do-react-hooks-replace-r...)
- Chris Achard: Can You Replace Redux with React Hooks?](https://dev.to/chrisachard/can-you-replace-redux-with-react-...)
One thing we haven't been able to replace, though, are classes that depend on refs. How to do this, since there's no instance with functions like there are with classes?
https://reactjs.org/docs/hooks-reference.html#useimperativeh...
If you ran into something that isn't quite covered by this hook, I'm also curious! Can you write a minimal example in codesandbox and share?
And yes, hooks need to be grasped, they're quite something else. But once you get the hang of hooks, they're very simple to understand.
OP seems to be a bit stuck in a hole.
If you end up in a situation where your hooks come loose and the code becomes a mess — just delete them all and rethink your code / component logic structure. Believe me, it all can be simplified. Just rinse and repeat until you are satisfied with the outcome. That's when the "aha!" moment comes.
At the moment I only use `useEffect`, `useState`, `useRef` — the rest is unneeded so far even in my large-ish application.
P.S. sorry for the puns!