The React State Hook
The React Hooks proposal comes with some built-in hooks that focus on doing one thing, such as providing state or context to a function component. You can also use these as building blocks to create your own.
In a recent post, I shared some personal thoughts on the
hooks proposal. This post will be more technical, as I go further into detail on
what I consider to be the most important hook: useState
.
As of writing this, hooks are still an experimental proposal. Nothing in this post should be considered final. Please keep this in mind. There is currently an open RFC where you can stay current on the proposal, and even voice your concerns if you have any.
Hooks are available in v16.7.0-alpha
of React. I've set up a
CodeSandbox that will get you going
quickly if you want to follow along with the example in this post.
React state today
If you want a stateful component in React, your only option at the moment is to write that component as a class. This has been a source of frustration for me. Often I will find myself spending a good chunk of mental energy contemplating whether or not I want to refactor a perfectly acceptable function component into a class merely to hold some state.
I'll convince myself that avoiding such a refactor is in my best interests. Eventually I'll find myself falling down a rabbit hole of state management strategies, libraries, and "other solutions".
If things really go south, I'll start asking if the component even needs state in the first place, as if it's something that should be avoided.
It sounds excessive, and you're probably right. But it's happened. And if you've spent a significant amount of time working with React, you may have found yourself on this wild goose chase as well (or maybe it's just me 🤔).
Adding state to a component should feel natural, but it's hard to feel natural when I'm writing a class.
Enter the useState
hook
With the useState
hook, function components can now hold local state.
import React, { useState } from 'react';
function StreetLight() {
const [color, setColor] = useState('GREEN');
}
useState
is a function that accepts an initial state and returns an array with
2 items:
- The current state
- A function that can be called to update the state
Because of the way array destructuring works, we can name the items returned by
useState
anything we want. There is no constraint imposed on us by the API. As
a convention, it seems that the React ecosystem is taking to the
[value, setValue]
syntax.
In the example above, color
is the state value and is initialized to
'GREEN'
. The setColor
function can be called to update that value.
Note that, unlink a class component, state in a function component does not need to be an object. Here it's just a string.
Another important note is that the update function, in this case setColor
,
does not merge the new state with the current, but instead overrides it
completely. This is different from how this.setState
works in class
components.
Updating state
The value of color
will be preserved between re-renders (more on this below),
unless the setColor
function is called with a new value:
function StreetLight() {
const [color, setColor] = useState('GREEN');
const slow = () => setColor('YELLOW');
return <button onClick={slow}>Slow down!</button>;
}
When the button is clicked, the function slow
will call setColor
with a
value of 'YELLOW'
. This will cause the StreetLight
component to re-render.
When it does, the color
variable will be updated to 'YELLOW'
.
Wait, what?
At first glance, you would think that every time StreetLight
renders, it calls
useState
with a value of 'GREEN'
. So how can color
be anything but
green?
A logical question. Here are a few lines from the React docs that may help ease you in to this concept:
"Normally, variables 'disappear' when the function exits but state variables are preserved by React."
"React will remember its current value between re-renders, and provide the most recent one to our function."
"You might be wondering: why is
useState
not namedcreateState
instead? 'Create' wouldn’t be quite accurate because the state is only created the first time our component renders. During the next renders,useState
gives us the current state."
But how?
It's not magic, it's JavaScript!
Put simply, React
keeps track
of calls to useState
for each component internally. It will also create a
mapping between the update function and the state value for which it updates.
The initial value passed to useState
is returned on the first render, but from
there React will return the correct value based on the mapping. It also uses the
map to know which slice of state to mutate when the update function is called.
If this seems confusing to you, you're not alone. I was baffled by how this
could work as well. My confusion only increased when I found out that
you can have multiple calls
to useState
in the same component:
function StreetLight() {
const [color, setColor] = useState('GREEN');
const [broken, setBroken] = useState(false);
// ...
}
Yes, you can do this to your heart's content.
How does React keep track of the state?
In order for all of this to work, React expects that you follow a few rules:
- Only call hooks at the top level of a function
- Only call hooks from function components and custom hooks
React imposes these rules because it relies on the call order of hooks to manage data properly. This may seem fickle at first, but these rules aren't hard to follow. And quite frankly I can't think of a scenario where you'd want to break them.
In order to internalize how React manages hooks in your components, I highly recommend reading this Medium post by Rudi Yardley. It was crucial in my learning process. 📚
And here's a psuedo-implementation of how React manages hooks, originally posted by jamiebuilds on Twitter.
Wrapping up
I consider useState
to be a building block. You can use it as-is to provide
state to your function components, or you can use it to abstract stateful logic
out into custom hooks!
I believe custom hooks are going to be the biggest superpower React developers
will gain when v16.7
lands, and useState
is the foundation. The community is
already sharing some
awesome stuff with custom
hooks and this pattern will only grow exponentially.
I hope you found this article helpful. Please reach out to me on Twitter if you have any questions, and as always, happy coding!