Early last year React 16.8 was released, bringing with it the addition of hooks
. This introduction of hooks gave developers the ability to take many of the useful features of React (such as Lifecycle Events, Context, and State) available in class components
and use them inside of function components
.
In this article we will take a look at a few ways we can use hooks to manage state within function components.
If you’d like to know more about the How’s and Why’s of using function components with hooks vs. class components, check out React’s Introduction to Hooks
The application built in this tutorial is available on github
The useState
Hook
The useState
hook allows you to declare state variables in a function component. This data is preserved on re-renders by React exactly like a state variable in a class component is preserved. To use the hook we simply have to import it and call it, passing in an initial value.
Let’s give it a try!
Declaring a state variable with useState
First, let’s declare a state variable named points
that we will use to hold a numeric point value that will be used by a point counter.
import React, { useState } from 'react'
function Points() {
// Creates a state variable named 'points' whose initial value is 0
const [ points, setPoints ] = useState(0)
// ...
As we can see, useState()
takes one parameter, the variable’s initial value. In this case we set the initial value to 0, however we could have assigned it an Object, Array, String, etc… Whatever you fancy!
The return value of useState()
is an array containing two items. The first is the most recent value of that state variable. The second is a function used to update that state variable. We use destructuring to grab both of those items out of the return.
Updating a state variable
Now we can use the setPoints()
returned by useState()
to update the points
state variable!
// ...
const [ points, setPoints ] = useState(0)
return (
<>
<button onClick={ () => setPoints( points + 1) }>Add</button>
</>
)
// ...
In the code above, every time the user clicks the Add button, the points state variable will be incremented
by one.
Putting it all together
Okay, so now we have a state variable and have the ability to update that variable! Let’s put those things together. Below is the completed Points component. Notice we can call setPoints()
from within another function in the component, resetPoints()
in this case.
import React, { useState } from 'react'
import '../assets/scss/Points.scss'
function Points() {
// Creates a state variable named 'points' whose initial value is 0
const [ points, setPoints ] = useState(0)
// Function that will reset the point count
const resetPoints = () => setPoints(0)
return (
<div id="Points">
<p className="title">Points (useState)</p>
<hr className="divider"/>
<div className="pointsContainer">
<div className="buttons">
{/* These buttons use the setPoints function to update the state variable's value */}
<button className="button add" onClick={() => setPoints( points + 1 )}>Add</button>
<button className="button subtract" onClick={() => setPoints( points - 1 )}>Subtract</button>
<button className="button reset" onClick={resetPoints}>Reset</button>
</div>
<div className="outputBox">
{/* Output the points variable */}
<p>{ points }</p>
</div>
</div>
</div>
);
}
export default Points
Pretty simple stuff, right?
Using hooks, we have successfully set up and updated a state variable within a function component. Now you might be wondering, “What if I have a really complex state in my component with deeply nested data? Is there a cleaner way to set up the state?“
In the next section we’ll take a look at an alternative to useState()
that addresses this exact problem!
The useReducer
Hook
The useReducer
hook provides the ability to manage state in a way that is very similar to how Redux manages state with reducers and dispatched actions.
If you aren’t sure what a reducer is, it is basically a function that updates a state based on an action that gets sent to it. For more detailed information, check out Redux’s documentation on reducers
To use useReducer
we have to import and call it, passing in a reducer and the initial state of the component.
Setting up the state and reducer with useReducer
Let’s start by creating a simple reducer, which will define how to update the state based on certain actions, and tell the component to use that reducer.
import React, { useReducer } from 'react'
// The initial state of the component
const initialState = { points: 0 }
// The reducer we are going to use
function reducer( state, action ) {
switch ( action.type ) {
case 'add':
return { points: state.points + 1 };
default:
throw new Error();
}
}
function Points() {
// Sets up a reducer to handle the state
const [ state, dispatch ] = useReducer( reducer, initialState );
// ...
In the code above, we set up our initial state to be an object with a single key, points
, whose value is 0. This example is simple, but useReducer
really shines as the state gets more complex.
The hook takes in a reducer
as its first argument and the initial state
as its second argument.
With this all set up, we now have a reducer available to the component that is ready to handle actions and maintain state across re-renders!
useReducer
can also take in a third argument, a function that allows you to lazily initialize the state. This can be useful if you would like to calculate the initial state outside of the reducer. Also, because this initialization function is outside of the reducer, it may be used later on to reset the state. See the docs for more info.
Dispatching actions to the reducer to update the state
useReducer()
returns an array with two items. The first being the most current version of the state and the second a dispatch
function. This function is used exactly the same way it would be in Redux.
// ***
return (
<>
<button onClick={() => dispatch({type: 'add'})}>Add</button>
</>
);
// ***
When the button is clicked, the dispatch
function is fired with a type of add
, which gets caught by the reducer and results in state.points
being incremented by 1.
Putting it all together
Awesome! Now let’s put all of that together. Here is a completed component using these concepts.
import React, { useReducer } from 'react'
import '../assets/scss/Points.scss'
// The initial state of the component
const initialState = { points: 0 }
// The reducer we are going to use
function reducer( state, action ) {
switch (action.type) {
case 'add':
return { points: state.points + 1 };
case 'subtract':
return { points: state.points - 1 };
case 'reset':
return { points: 0 }
default:
throw new Error();
}
}
function Points() {
// Sets up a reducer to handle the state
const [ state, dispatch ] = useReducer( reducer, initialState );
return (
<div id="Points">
<p className="title">Points (useReducer)</p>
<hr className="divider"/>
<div className="pointsContainer">
<div className="buttons">
{/* These buttons use the dispatch to update the state */}
<button className="button add" onClick={() => dispatch({type: 'add'})}>Add</button>
<button className="button subtract" onClick={() => dispatch({type: 'subtract'})}>Subtract</button>
<button className="button reset" onClick={() => dispatch({type: 'reset'})}>Reset</button>
</div>
<div className="outputBox">
{/* Output the points variable */}
<p>{ state.points }</p>
</div>
</div>
</div>
);
}
export default Points
So there we have it!
A function component using a reducer to manage state. This way of managing state is preferable over useState
because of some performance boosts that are mentioned in React’s documentation on the useReducer
hook.
Conclusion
As you can see from the examples above, React’s implementation of hooks has not only brought over the useful feature of statefulness from class components to function components, but has also made it pretty easy to do!
We looked at two ways to keep state in a function component. First, using useState
and then using useReducer
. In general, useReducer
is the way to go when dealing with larger data sets in the state, while useState
is great for components with a simpler state.
The next step to this would be sharing state across multiple function components! In my tutorial Shared State with React Hooks and Context API I cover one of the more popular ways out there to achieve this. Give it a read if you’re interested!
Thanks for reading, I hope you enjoyed!