All code from this post can be found in a codepen collection.
A functional component (also sometimes referred to as a “stateless” component) is a method of defining React components with only a render method. These components still take in readonly props and return some JSX, but until now have had no means to perform any stateful logic. The following is a simple functional component that creates a button for canceling a user’s account:
1 2 3 4 5 6
The component takes in a single prop
onClick that is called when the button is clicked.
Adding Stateful Logic The Old Way
Let’s say that we want to add some stateful logic to that component. Marketing has started complaining that users clicking our current “Cancel Account” button contribute to a loss of revenue, and we need to slow that loss down to appease investors this quarter. We get design involved and decide to prompt the user several times to confirm their cancellation. We’ll need to keep track of the number of clicks and the current prompt in state.
Here’s how we might do that on February 5th, 2019, using class components:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
Wow! We’ve nearly tripled the size of our original component here. In this stateful world, we needed to extend the
React.Component class, define a constructor to set our initial state, update our state when the button is clicked, and add the
componentDidUpdate lifecycle method. The
componentDidUpdate method is called on every re-render, so we first check to see if the number of
clicks changed before taking any action. If it did, we check to see if we have more messages than clicks and update the prompt text; otherwise, we call the original
onClick function from our props and, unfortunately for our sales goals, churn another user.
This is a lot of boilerplate and has a tendency to get complex really fast. If only there was another way!
“Well, actually, Papa Larry,” I hear you interjecting from behind your monitor, “we could do this without a lifecycle method and only one piece of state.” My dear friend. Yes, this code is slightly contrived so that I can show you all the main features of hooks with a fairly straightforward example. Just keep your susurruses to yourself until after the show.
Adding Stateful Logic the New Way
This is where Hooks come into play. Let’s fast-forward from early evening in the American Midwest on February 5th, 2019, to late evening, when suddenly React 16.8 was released and it was officially titled “The One With Hooks.”
Let’s take our original functional component and add state with Hooks:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
Our Hooks implementation is about half as long as our class implementation. I would argue that it’s also significantly easier to read. Let’s break this down bit-by-bit to discuss each piece of the hooks API:
At the top of our function, we call the
useState method to declare two state variables:
useState takes in an initial value and returns a state variable and setter method, which we access locally using array destructuring. In this case, we set the initial state of
0 and leave
Behind-the-scenes, React is using our component’s scope to create and track these state variables. We must always define these variables in the same order when this function is executed, or we’ll get our variables all mixed up and our logic won’t make any sense.
1 2 3 4 5 6
useEffect method is essentially a replacement for the
componentDidUpdate lifecycle methods. It takes in a function that will be called after every render. Here we take advantage of closures to test the value of our
clicks state variable and use
setButtonText to update our
buttonText state variable. The second argument to
useEffect is an array of state variables to check - if none of the given state variables were changed, the effect will be skipped.
We can call
useEffect as many times as we want in our component. This allows us to create a clear separation of concerns if we need to define several different effects.
1 2 3 4 5 6 7
This is our same old render logic, but in this case we’re using the
setClicks function returned to us by
Design and marketing like this concept of delaying an action and just changing the text so much that they want to use it all over the site. Now we have stateful logic that needs to be reused. This is where the concept of “Custom Hooks” comes in:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
Here I’ve created my own hook called
useTextByCount that abstracts away the entire concept of the
buttonText state variable. We can use this custom hook in any functional component. Abstracting stateful logic is a tall task in class components, but it’s completely natural using Hooks.
Hooks are the result of the React maintainers responding to the way React developers want to write code, enabling us to use powerful stateful concepts in a cleaner, functional system. This is a natural next step for the React API, but it’s not going to deprecate all your class components. Hooks are completely optional and backwards compatible with current React concepts, so there’s no need to make a Jira ticket to refactor all your components tomorrow morning. Hooks are here to help you write new components faster and better, giving you new options when you need to start adding state to that simple button component.