A brief introduction
Unless you live under a rock (not sure that would be comfortable 😂), you've heard of hooks and how they have exploded in popularity with the community.
It's easy to get caught up in the hype and not really understand how we got here and why. If you haven't watched the original video that Dan did at ReactConf in 2018, I recommend giving it a quick watch. The video explains what hooks give us and why they made them.
How did we get here?
React has two ways to develop components: classes and functions. The irony here is that up until recently the JavaScript community had complained for so many years why don't we have classes in JavaScript and even built things like createClass
/etc. Recently the community has flipped the other direction going full on functional. Why did this happen? Well maybe its the fact that as humans we often want what we can't have or maybe its just because we realized that once we got them they don't really fit the JavaScript prototypical inheritance model.
Prior to the launch of hooks, the functional component development approach was limited to very basic components since there was no way to leverage state or life-cycle functions. When I first saw hooks, I had flash backs to Angular 1.x code with huge functions. Granted thats a huge no-no but let's be honest in the real world it will happen eventually. I personally favored the class approach because it kept things very organized so I wasn't sold on the functional approach at first but the more I used them the more I enjoyed them.
History time!
As I used hooks more and more, I started enjoying them more. One day when I was using hooks, I thought to myself how did we get here and as I started thinking about it, there is quite a history here.
The problem statement is simple, how do we share the code between several components? After all code re-use is one of the first things we are often taught when we start writing code. This is typically code that handles generic things like window resize events, scroll events, etc. The hard problem is how do you make this as explicit as possible so there isn't 'magical' bits going on.
Directives
In Angular, there is a concept of directives which allow you to decorate elements with bits of functionality. For example, I could do something like:
<div
[appWindowResize]="myBreakpoints"
(windowWidthChanged)="setNewSize($event)">
</div>
When I first started writing React, this was one of biggest things I missed. One of the biggest issues with this approach is its difficult difficult to chain these together with shared scope.
Mixins
The early versions of React used a createClass
method and had a concept of mixins. Not too long ago React put out a article called Mixins Considered Harmful. The premise behind the article is that as components grow with several mixins it becomes 'incomprehensible' to understand whats going on. A component with mixins might look something like this:
var Button = React.createClass({
mixins: [WindowReisze, Orientation, Animation, Tap, Drag]
});
in the body of this component, now you have all these methods that are there now magically. Where did these methods come from, what if they have name overlap, and so on.
On top of all of this, React favor a more functional approach so the createClass
was not really good approach for this.
Decorators
Once classes landed in JavaScript, we instantly started bringing concepts from other languages like C#. Angular doubled down on this approach in Angular 2.x making the entire framework driven off decorators.
@Component({ ... })
export class MyComponent {
@Input() name: string;
}
Honestly, I don't have a problem with decorators but we hadn't even let the paint dry on classes before we started adding all these other language features and well they had to change. Now all of this code that relies so heavily on decorators is likely gonna have to be re-thought.
The problem with decorators for React is the same problem I mentioned above, React favors a functional approach and when you apply decorators to functions, it ends up looking something like:
@bind
@resize
function bar() { ... }
and it becomes hard to comprehend again.
Higher Order Components (HOCs)
Higher order components came next. They gave us similar features as decorators but didn't require the new language feature. However they had the same problem as decorators, they are hard to reason what's going on with them.
export default withRouter(
connect<{}, {}, {}, DashboardProps>(
mapStateToProps,
mapDispatchToProps
)(Dashboard)
);
In the example above, there is only two HOCs wired up and I already couldn't tell you what exactly is going on.
Render Functions
Once we came to the realization that all these higher order approaches suffered the same problem of being difficult to reason with, the community came up with the 'render functions' approach. While this approach is more explicit and favors a declarative approach that feels natural for web dev, it gets out of control quite quickly.
<Route>
{route => (
<Permissions>
{roles => (
<Query variables={{ foo: true }}>
{data => (
<Dashboard {...data} {...route} {...roles} />
)}
</Query>
)}
</Permissions>
)}
</Route>
This approach is more explicit but thats comes with a price too.
Wrapping up...
As you can see all of these approaches come with a price. Hooks give us a fresh look at making composable components with high code re-use but its hard to tell how they will play out in the real world after several developers have worked on the same bits of code in a large code base
I hope you enjoyed the post, if you liked it follow me on Twitter and Github for more JavaScript tips/opinions/projects/articles/etc!
Top comments (1)
cool post, and do you know concent [ github.com/concentjs/concent ]? a progressive&high performance state management for react, code less and do more, hope you like it