I'll draw an analogy.
---
Why did async/await replace callbacks? They are really the same thing, aren't they? Aren't callbacks good enough?
Async/await allowed programmers to write asynchronous code as if it were synchronous.
---
Why did hooks replace classes/imperative? They are really the same thing, aren't they? Aren't classes/imperative good enough?
Hooks allowed programmers to write stateful code as if it were stateless.
function MyComponent() {
const [value, setValue] = useState(false);
const buttonClass = value ? "on" : "off";
return (
<button
className={buttonClass}
onClick={() => setValue(!value)}
>
{String(value)}
</button>
);
}
This is functional code, without scary, hard-to-reason side-effects. Except for one limited part, which operates in a stateful way.I argue that this is simpler than the equivalent imperative-DOM modification code, and the gap between these only increases with real programs.
This isn't just a weird "web bro" thing; the hooks paradigm is useful (in theory and practice) to every UI platform.
I even wrote an implementation (closed source) for hooks in Angular. It was lovely.
It also shows that although there might be some learning investment required to use the more modern version of both, it's well worth the effort. I can't imagine going back to callback-riddled javascript
It is some pseudo DSL with hidden hard to reason mutable state.
let result;
for (const item of items) {
result = await foo(item);
if (result) {
break;
}
}
is not synchronous code. But you can write it very close to the mental model of synchronous programming.The same is true of hooks. The code is not stateless, but you can write it very close to the mental model of stateless programming.
That is why -- despite them being optional -- hooks have become incredibly popular.
I don't know exactly how – never had the time to properly research it – but, my current guess is that the hooks code taps into the scheduler/dispatcher – which handles the execution of the function representing the component – and stores value and logic somewhere. It uses the order of these calls as keys – you don't have to specify them – and provides the stored values as return values of the call of the hooks functions.
Hooks are escape hatches from the functional paradigm of React's components. You are always providing new values, and it decides when to store the updated version – mostly based on the deps array. You then get back what you stored. On the surface, it's still basically functional, but the actual logic is not. It's more like a repository of code and values.
It's still so very functional programming-inspired, even if the execution engine (scheduler/dispatcher) isn't that much like the Monad runtimes of most functional programming languages and the various Hook "monads" don't get captured in even the return type of functions (much less the parameter types) and get elided away. (It could be more "traditionally JS monadic" if it [ab]used async/await syntax and needed some fancy return type, even though the concepts for hooks don't involve Promises [or Futures, being the slightly more common FP Monad name]. Though also, from Typescript patch notes, I've heard React is exploring [ab]using Promises for something like that in the near-ish future.)
Monadic bindings needing to be in-order, just the like "Hook rules", isn't even that strange from an FP perspective: there can be a big difference in which of two Promises is awaited first. There can be a big difference in which IO binding executes first (you don't want to input something before the prompt of what to input is printed).
"What's this .map() nonsense? The indexed for-loop always made sense to me."
It isn’t only front-end developers who are prone to this: 20 years ago, before FP became a mainstream thing, being able to do pointer arithmetic was seen as the differentiator between a “smart programmer” and a “bad programmer” (https://www.joelonsoftware.com/2006/10/25/the-guerrilla-guid...).
Was React great and fun to work with? Yes, but then Redux came along and made something simple into something stilted. Our team used Mobx instead, which was simpler IIRC.
Similarly it’s been a couple of years, but I remember React Hooks being a bit complicated for the value we got out of them.
Ultimately, it’s hard to tell if something is an improvement, or a time suck to allow the elites to stand out. I stopped playing the game and stepped away from front-end development, as have many of my experienced colleagues.
I’m assuming things have settled as Big Tech focusses on profitability rather than funding an endless parade of frameworks, and the IQ-signalling will become less prevalent as generative AI competes with the brainiacs. When that happens, I’ll dip my toes back in the world of front-end development.
Though FWIW, MobX is far more magical than hooks.
That’s the magic of react. The problem is no longer manually managing transitions between state but state itself.
That’s why state management is so critical in the react ecosystem.
Further, react has delegated two key features to userland: side effect and state management.
Personally, I don’t want react to solve either of these problems because it’ll just turn into the disaster that is react server components: feature development driven by corporate interests (eg vercel).
Here's how the React docs introduce it:
"React is a JavaScript library for rendering user interfaces (UI). UI is built from small units like buttons, text, and images. React lets you combine them into reusable, nestable components."
Hopefully you can appreciate the difference.
If this was actually how it worked, I'd have much less problem with react. What I mean, specifically, is that not all function dependencies are explicit. This makes it look like the state is passed into some function. And then the view is returned, depending only on state. In reality, half the state is passed in (as in the argument to a function call), and half comes out of some mysterious hook storage structure. If the state was really a function argument, you could easily inspect the whole state in a debugger. Instead of... I'm not sure how you'd actually do that.
if you've followed along with the react team's discussions on server components, it is very clear that vercel implemented the react team's vision and less so the other way around.
as someone who has been using them extensively, I understand why. they're delightful.
vercel does control next.js which is the only framework to completely implement RSC, but as other frameworks catch up, RSC will have a wider surface area.
Hopefully you'll click on it someday
2) Components have behavior. In Class based components they were implemented as methods. Simple enough, classes have methods. If you think about an iteration of rendering the ui tree, it goes down the tree and calls the mount method for each component in the ui. With hooks, instead of calling a method on an instance, it's implemented as an inner function of a component that is also a function instead of a class. Instead of explicitly calling the mount method on a class instance, calling a function that's a component automatically runs it's inner mount method. The way that it knows the which inner function is a lifecycle hook is because you import something like 'useEffect' from the react library. And that global useEffect function knows the current component because the renderer sets it as it's going down the ui tree. Hooks are better because they can be written as arrow functions (more concise, cleaner), and because you're calling a framework function, that function can have more complex behavior like how useEffect can track when to update because it's being passed in an instance of something like an internal state variable.
https://nakedjsx.org/documentation/#getting-started
EDIT: clarity