useState - A Primer
Responses (0)
Hey there! 👋 Want to get 5 free lessons for our The newline Guide to Modernizing an Enterprise React App course?
Function components have come a long way since React v0.14. Introduced as a simpler syntax for defining stateless, presentational components, function components can now handle state via the useState
Hook. This means you can define any component, stateful or stateless, with functions, and you no longer have to deal with the extra, unnecessary code that comes with classes. In fact, if you choose to write all your components as function components, then the bundle size of your React application significantly decreases.
To understand how the useState
Hook lets you manage state in function components, we must first revisit class components.
Revisiting Class Components#
When you create a class component, all of the component's state is stored within a single object. Within a class constructor, this object gets assigned to the component's state
instance property, like so:
To update a single value (or multiple values) of the component's state, call the component's setState()
method:
Try it out in a CodeSandbox demo here.
Defining Stateful Components with the useState
Hook#
With the useState
Hook, you no longer have to define stateful components with the class syntax. Here's how you define the <App />
class component as a function component:
Try it out in a CodeSandbox demo here.
Notice how much smaller the component's definition has become.
Anytime you write a class component, you end up repeating the same boilerplate code:
Explicitly telling the class component to inherit from
React.Component
(class ClassComponentName extends from React.Component
).Calling the parent class constructor with
props
inside the class component's constructor (super(props)
).Binding the correct context to all class methods that reference the
this
keyword (this.classMethodName = this.classMethodName.bind(this)
). You can avoid this by defining class methods with the experimental public class fields syntax, but that requires an extra polyfill to be installed.Referencing
this.state
whenever you need a value from the class component's state.
The useState
Hook is a special function that lets you add state to function components. When called, this Hook returns a state variable and a function for updating the state variable's value.
The state variable contains the current state. The useState
Hook accepts only one argument: the initial state.
initialValueOfStateVariable
can be anything: a string, a number, an object, etc.
Conventionally, you unpack the state variable and update function from the array returned by the useState
Hook. Additionally, you name the update function after the state variable, but prefixed with the word set
. For a state variable named isMessageShown
, its corresponding update function should be named setIsMessageShown
.
The update function behaves similarly to the setState
method:
An update to the state causes a re-render of the component (and its child components).
Both accept the same argument (either a new value to update the state variable with, or a function that provides the most recent state as an argument and returns the new state). The
setState
method can accept an additional callback function that runs after the state changes have been applied and the component has been re-rendered.Both run asynchronously. State updates are not guaranteed to be applied immediately.
Both immutably update the state.
However, there is a key difference between the two. While the setState
method shallowly merges the component's new state with its old (most recent) state, the update function completely replaces the old state with the new state.
For example, given an initial state of...
Calling...
Leaves user
and count
intact, but only changes the value of isMessageShown
. The current state becomes a new object that contains the modified isMessageShown
and the original user
and count
:
Try it out in a CodeSandbox demo here.
Note: For state with deeply nested objects, you may want to use the spread operator to preserve the nested values.
On the other hand, the update function completely wipes out user
and count
and only keeps the modified isMessageShown
in the new state:
Try it out in a CodeSandbox demo here.
As a best practice, you should...
Keep all
useState
Hook calls at the top of the component's function body.Break up large component state into multiple state variables. Rather than keeping all component state in a single object, create a state variable for each piece of the state that will be updated. For example, given a component state of...
{
isMessageShown: false,
user: {
name: "Hello World",
location: {
street: "123 Fake St.",
city: "San Francisco",
state: "California",
country: "United States"
}
},
count: 0
}
Define three state variables (and their update functions), one for each of the object's top-level properties:
const [ isMessageShown, setIsMessageShown ] = useState(false);
const [ user, setUser ] = useState({
name: "Hello World",
location: {
street: "123 Fake St.",
city: "San Francisco",
state: "California",
country: "United States"
}
});
const [ count, setCount ] = useState(0);
Refactor reusable stateful logic into a custom Hook that can be used across multiple components. Unlike state in class components, state in function components is not tightly coupled to a specific component.
Next Steps#
To learn more about the useState
Hook, check out the second tutorial in the six-part YouTube series on React Hooks by Paige Niedringhaus, a Blues Wireless Staff Software Engineer and author of The newline Guide to Modernizing an Enterprise React App.
