React has edges. Hooks are an anti-pattern with all kinds of footguns. Suspense is also a crazy anti-pattern by literally throwing and catching errors as a means for communicating between parts of the framework.
And this isn't an issue of me not understanding React or hooks, I would consider myself to be as expertly acquainted with hooks as it's possible to be.
Now, do I still use React for everything? Yes. Do I prefer hooks to class components? Also, yes.
> Suspense is also a crazy anti-pattern by literally throwing and catching errors as a means for communicating between parts of the framework.
Okay I can criticize React and its weird solutions until the cows come home, but this is a pretty weird one because it’s entirely an implementation detail. No one working with Suspense ever needs to know that’s how it works unless they’re building a library to be compatible with its behavior. Otherwise it’s just trivia.
Wouldn't the debugger break on exception? So any exceptions used in "nonexceptional" situations have a potential to "pollute" the debugging experience?
Admittedly, I haven't tried debugging React 18 yet, so I don't know if that actually happens in practice.
Only if you set it to break on handled exceptions/Promise rejections. This isn’t new behavior in React 18, it’s been the mechanism used for Suspense since it was introduced. If you have used Suspense and haven’t had unexpected debugger pauses, that’s what you should expect going forward.
In practice, because they don't work the way most people think they work, and because it's really easy to screw up using them.
Hooks are called every render, which is a relatively transparent process that happens all of the time. All hooks have to be called every render, otherwise you're breaking the rules of hooks. The reason why is because the only way hooks know which hook they even are, is the order in which you call them.
For example, if I have
const a = useRef(true);
const b = useRef(false);
The only way react knows that the first value it should return the next time it renders is what I'm expecting to be value `a` is because that useRef is called first every render. These are all kinds of rules and assumptions about hooks that make them not behave at all like normal functions. I don't think people understand that, for useRef, for example, those two lines of code are run every render, passing in the initial starting values, which then react disregards after the initial render, and maintains a mapping of ref and state and memo etc values all by the order the hooks are called in. I see people use hooks in callbacks all of the time and just fundamentally not understand that they're doing it wrong.
Then there are all of the closure problems with useEffect and useCallback that I see people really struggle with.
And useState...lordie. The fact that it defers setting the state value, whereas useRef will immediately have its value updated. Deferring useState has caused so many bugs.
I don't like them because they hide program state in innocuous-seeming function calls. Want to call useContext in a helper? It'll work, until it doesn't. Want to display a loading spinner? You'd better not have any useState calls below it. Want to see what hooks a function will use? You have to run it or go through each line of every piece of code it calls, they're not in any way declarative.
The programming paradigms I like best are declarative and make invalid or undefined behaviour impossible to represent. Hooks do the opposite: they're procedural, make it very easy to write a program that compiles with subtle errors, and don't have compile time checks to stop bad code.