This was a really good read; Dan’s blog posts have on the whole been really enlightening and I’m glad he’s started doing them.
Not that odd to me for this post to focus on Hooks while practically writing class components out of the React narrative. I think Dan makes a strong implicit case for Hooks being a much more natural fit for React’s conceptual model than classes. The “composition” patterns that emerged from the React community (container/presentational separation, higher-order components, render props; the former two, I may add, were first popularized by Dan himself) were papering over the fact that it’s difficult to make concerns compose well in component classes (lol remember mixins?).
The tradeoff with these patterns was that they were still clunky. You ended up with big “stacks” of composed HOCs or nested render-prop components that weren’t always easier to follow than the classes they replaced abstracted away. Where was this prop injected? Can I access this bit of state in this setter? Hooks are a much purer distillation of the idea of adding state or effects to a component than classes, which is why I think this post chose to ignore the latter; it’s more ceremony diluting the ideas Dan wanted to present.
My kneejerk reaction to Hooks was “How are these composable?” At first blush, they certainly look like they encourage entangling concerns and breaking container/presentational separation. But what they really do is add a new fundamental unit of composition to React beyond the component. Now a developer doesn’t have to write a component, worry about where it lives in the component tree, etc. when she wants to factor out a particular piece of domain logic, because a smaller unit of abstraction now exists.
Plus, by enforcing rules about how they’re used (i.e. never inside control-flow statements), Hooks end up being static, declarative assertions about what “capabilities” a component has. If you squint hard enough it almost looks like a runtime row-typed effect system (PureScript folks, feel free to point and laugh at me for this one, but ML-style algebraic effects were a big influence on the design of Hooks).
tldr: post good, hooks good
With JSX and now hooks it really looks like React has turned into a declarative DSL rather than a usual language/framework, which IMO is a very good thing, and I think much of the pushback hooks are getting is because of this kneejerk reaction from people who don’t understand this.
The problems I see with the hooks DSL is that the rules about how they’re supposed to be used don’t really fit into a traditional type system like the one provided by TypeScript, so the only way to enforce them is using a linter. These constraints look kind of foreign and arbitrary if you’re not familiar with how hooks work internally.
Also, hooks add another layer of magic on top of React’s runtime, which Dan described in this post, So I can definitely see people being wary of this new feature. It’s definitely better than what we had before, but it also comes with some cognitive costs, because the model is quite a bit different from your usual JS.
I find a lot of the complexity in React goes away when you take a data driven approach. For example, Reagent is a ClojureScript wrapper for React that adds the idea of reactive atoms. An atom is a data container that UI components subscribe to, when the contents of the atom change it notifies all the subscribed components to repaint with its new state. With this approach, you only care about the render step of the React lifecycle vast majority of the time because the UI simply renders the state of the data.
This approach potentially removes the need for VDOM entirely. For example, there’s an alternate implementation of Reagent called Mr Clean that uses reactive atoms to drive the DOM directly. All the computation and batching can be done against the data without involving the UI components at all.
This approach also makes testing much easier. The UI observes the data of the data via subscriptions, and it dispatches events to update the state. So, all the business logic can be tested without touching the UI at all.
Thank you for the link to Mr. Clean, would you consider using it instead of reagent entirely?
I don’t think this would be a replacement since Reagent provides access to React ecosystem. But for projects where that wasn’t a concern this could produce much smaller bundles since the compiler can do dead code elimination. The fact that the API is compatible is a big plus, you could start with Mr Clean and then move to Reagent if needed.