React Hooks: What They Are and Why They're Important
Sophie Alpert and Dan Abramov showcased hooks, React’s newest upcoming feature, at ReactConf this year. At first glance, I thought they seemed strange and frankly unhelpful. But as I’ve worked with them and worked through the spec and documentation, I’m now of a very different opinion. I - along with many others - think hooks are the future of React. In this first article in a 3 part series, I’ll discuss what hooks are and why I think they’re important for React ecosystem. The second article will deep dive into the specific APIs of each hook included in React. The third article will walk through refactoring a render prop component into a custom hook, in my opinion one of the most interesting aspects of the hooks specification.
I highly recommend looking at the hooks documentation on the React website. The React team has done a brilliant job providing documentation for this new feature. The ReactConf keynote presentation is also worth a watch. Additionally, there have already been many interesting observations and excellent articles written on hooks. I have included a selection of these in the Additional Resources section.
#What are hooks?
It should be noted that hooks are only a proposal and included in the React v16.7 alpha release as a preview. Certain aspects of their API and internal workings may change before their final release.
Over the past few years, there’s been a natural evolution in how React allows us to create components. The very first versions of React had the
createClass function. It would create components when passed an object following a very specific structure defining its state, propTypes, internal methods, render function, etc. There wasn’t a better way in the language to create components, either stateful or stateless. With the ES2015 spec bringing the
class keyword. React v0.14 included support for regular functions as stateless components. This made defining stateless components as simple as writing a function. After all, a stateless component’s output should be a function of its structure and props.
Fast-forward to hooks. Now, stateless and stateful components can be written as functions. Hooks give us the ability to utilize all of React’s features within the context of simple function components. Importantly, hooks are merely functions themselves. While they give us access to core React functionality, using them feels the same as using any other function. Almost everything in React is now a function!
#Hooks by Example
Let’s look at two React hooks, how they are used, and how their syntax maps from class components.
The first hook is
useState which gives us the ability to - no surprise - manage component state. Let’s start by looking at simple counter component using the class syntax.
There’s not much going on here. We set an initial value for our
count state in the constructor and have an
incrementCount method we can call when the button is clicked to increment that state. Still, for something as simple as a counter, there’s a lot of code we have to write to get it to work, including knowing about class constructors, making sure we don’t forget to call
super(props), and binding
this correctly to
Here’s that exact same component using a hook.
That’s so much more straightforward! I have a single call to
useState (by convention, all hooks start with the word
use to signify they are in fact hooks) which returns an array with two elements in it. The first is a reference to the state being managed, which we named
count. The second is a function to change that state, which we named
setCount. We can think of the
useState either. We can name them descriptively based on how we’re using
useState at the time. Additionally, any value passed into
useState when it’s called -
0 in our example - is used as the initial state.
Let’s talk about
setCount. It’s very similar to
this.setState in that it allows us to update our state over time. However, while
this.setState will merge any state changes for you,
setCount (or any state setting function from
useState) always overwrites the previous state.
incrementCount is now a function in our component instead of a class method.
useState hook. It gives us all the state management features of class components in our function components and will continue to be a building block for more advanced hook use cases.
useEffect is a hook that allows us to run and manage side effects in our components. Typically, one would use component lifecycle methods to accomplish this. Let’s see what this might look like with a class component.
Let’s break that down. First, we’re going to keep
messages in our component state. This will get updated as messages are pushed to us via our
fakeWebSocketLib. Second, we’re subscribing to our socket using the
id prop and a callback which will add any messages we receive to
this.state.messages. Third, we are using
compenentDidUpdate to check if the
id prop has changed between renders and, if so, unsubscribing to the old socket and subscribing to a new one with the new
id. Fourth, we make sure there aren’t any memory leaks and unsubscribe to the socket when the component is unmounted. For this example, we don’t care about what the component will render, hence the lack of code in the
Whew. That was a lot. And there are some gotchas in there. Someone new to React probably wouldn’t think to cover the
componentDidUpdate case. We have duplicate code in there too. We could DRY that up, but it would also mean another class method (and making sure we remember to call
.bind(this) on that method in the constructor).
Here’s the exact same component using hooks.
Isn’t that incredible? Our code is much more expressive now that we have a sequential description of what’s taking place as opposed to our logic being broken up amongst different lifecycle methods. It’s also DRYer and contains less boilerplate.
useState again to maintain the list of messages. Next, we call
useEffect and pass it a function to run for us, subscribing to our socket with
props.id. The subscribe callback will add any messages it receives to
setMessages. We also return a function inside
useEffect. This function will automatically be called when the component unmounts to clean up the socket subscription. Don’t forget the requirement that we need the subscription to be torn down and recreated any time
props.id changes. Notice the second parameter we pass to
[props.id]. We’re telling
useEffect to clean up and re-instantiate itself if
props.id, and only if
We went from 33 lines in our class down to 13 using hooks, but lines of code is a terrible metric. Instead, think about how much more clearly the hook example communicates what the component is accomplishing. We can perform side effects in a straightforward fashion providing both initialization and cleanup code in the same code block as well as utilize the output of other hooks. If your intuition is telling you hooks are a big deal, you’d be right, but for more reasons than just code clarity and readability.
#Why hooks are important
In order to understand why hooks are important, we need to take a step back to talk about composability. React was one of the first view libraries to model user interfaces as a composition of components. With React, having self-contained code artifacts with clearly defined APIs provided the foundation for cleanly separated building blocks for our user interfaces. We construct our UI by starting small and building up as we compose larger and larger components. This component architecture is so effective, almost every other view library now follows similar models.
If we consider the types of components we create, they fall into two general categories. The first category contains what are sometimes described as “stateless” or “dumb” components. These components are typically functions that hold no state, do not interact with their environment except by accepting props, and are chiefly concerned with displaying our UI. The second category contains “stateful” or “smart” class components which hold business logic to interact with the environment, handle data fetching, provide user interactivity, etc. It’s important to note that while our UI code is easily composable using our stateless components, our business logic is actually locked into the stateful components in which it is written. How do we then share this business logic (read: how do we compose this logic) across our application? Through React’s lifetime, this has been handled in a few different ways.
If we go back to the beginning of React, when components were created via
React.createClass, mixins were the primary mechanism for sharing business logic amongst components. Mixins were built into React and could be utilized to easily inject methods and state into components. As time went on and mixins were more commonly used, multiple drawbacks became apparent. Mixins created implicit dependencies where it became difficult to tell where logic was coming from, especially if one used multiple mixins in the same component. Mixins could also collide with each other if the method or state names were ever the same. Further, once React moved to using
class to define components, there was no way to use mixins anymore. They were deprecated swiftly.
But composition was still a necessity. A new pattern arose in the community, spearheaded by Redux’s React bindings, to share logic: higher order components or HOCs. HOCs are functions that take a component and wrap it in another component while injecting props into the original component. How the wrapping component behave and what the props are depends on the desired functionality of the HOC. It sounds confusing, but HOCs are actually quite simple to use. For those that have used Redux, its
connect function is an HOC. HOCs can accept options themselves and multiple HOCs can be strung together using a
compose function. They are very powerful, but they suffer from many of the same downsides as mixins. They create implicit dependencies where it’s not always clear where props are coming from. They can also clobber one another if two HOCs try to inject the same prop.
To provide a more flexible paradigm, the idea of render prop components emerged. With render props, instead of allowing other code (HOCs, mixins) to inject props into a component, props can be named and even renamed when used by the developer, providing greater clarity as to where logic was originating. These props can also be spread out across a tree instead of just being injected into the immediate child. One significant downside is using multiple render prop components can be verbose and unwieldy. They can significantly increase the number of components written in the component tree; patterns are just now starting to emerge to mitigate this issue. Their syntax can also be confusing, especially for those less familiar with React. While render props have become the most popular paradigm for reusing logic as of late, they can still be difficult to work with and still rely on class components and component lifecycle methods under the hood to provide much of their functionality.
Hooks step into this environment and provide a better alternative for sharing business logic. We no longer rely on components to encapsulate our logic which injects more complexity into our component tree, either implicitly or explicitly. Instead, hooks exist as functions that can be called within components, shared across components and themselves composed into more complicated hooks - oh yes, custom hooks exist and they are wonderful - without affecting the component tree. All calls to hooks are explicit so dependencies aren’t hidden and give the developer naming flexibility so there’s no prop clobbering. Hooks are brand new, so there will undoubtedly be rough edges and scenarios we haven’t even considered yet but they will only improve as they reach full community adoption.
The initial look and feel of hooks is fantastic. They are the most composable paradigm for sharing business logic and allow everything in React to be encapsulated as functions, both UI components and business logic. This move towards functions has been the evolutionary trend of React throughout its life and hooks are a natural outcome of that evolution. They are incredibly powerful and I can’t wait to see how we as the community use them to write better software.
I will be writing two more articles on hooks, one walking through the APIs of the different hooks React provides and another on converting a render prop component to a hook. In the meantime, there are many helpful resources already out the in the community and a handful are included below:
- Hooks documentation
- ReactConf keynote presentation
- ”Making Sense of React Hooks” - Dan Abramov
- Collection of React Hooks
- ”A Simple Intro to React Hooks” - Dave Ceddia
Have thoughts on hooks? Chat with me about them on Twitter!