React useState by example
We'll learn about React's useState hook in this example. It is a React hook that allows you to utilize states in your functional components. This was not feasible prior to the release of React 16.7.
We'll show how to leverage the built-in hook useState()
to enable component functions to have local state.
In the previous tutorial, we've cloned our React/Axios/Django project and installed the project's dependencies including React 16.7 which provides support for React Hooks.
Our front-end application has two React components:
CustomerCreateUpdate
: for creating and updating customers,CustomersList
: for listing customers
The API requests sent with Axios are encapsulated in the CustomersService.js
class.
Classes are used by both components. Before we get started, let's go over some theory regarding the useState()
hook.
Why useState
Prior to React 16.7, if you were using functional components in your app and suddenly needed to add some state to your function, you had just one solution: transform your function to a class that extends React.Component
, then use this.state
to add initial state and setState()
to update it.
With the most recent versions of React (for example React 18), you may still utilize functional components while taking use of all capabilities like state and life-cycle functions through hooks.
In this article, we'll focus on the useState()
hook.
What Does useState
Exactly Do
The useState
function is a built in hook that can be imported from the react
package. It allows you to add state to your functional components. Using the useState
hook inside a function component, you can create a piece of state without switching to class components.
There some differences between handling state in functions vs classes:
- In class components, the state is an object accessed using
this.state
; You simply add properties on this object to add an initial state and then usesetState()
to change it later. - In function components and using
useState
; the state is not necessarily an object. It can be an array, a number, a boolean or a string, etc. You can make multiple calls touseState
in order to create a single piece of state with an initial value but also the function that's used to change that state later.
Understanding useState
by Example
Now, let's see an example of useState
to create a stateful function component.
This a truncated example of the CustomerCreateUpdate
component:
import React, { Component } from 'react';
import CustomersService from './CustomersService';
const customersService = new CustomersService();
class CustomerCreateUpdate extends Component {
constructor(props) {
super(props);
}
componentDidMount(){}
handleCreate(){
customersService.createCustomer(
{
"first_name": this.refs.firstName.value,
"last_name": this.refs.lastName.value,
"email": this.refs.email.value,
"phone": this.refs.phone.value,
"address": this.refs.address.value,
"description": this.refs.description.value
}
)
}
handleUpdate(pk){}
handleSubmit(event) {}
render() {}
}
export default CustomerCreateUpdate;
This component extends React.Component
. This allows the component to use the componentDidMount
life-cycle event and this.refs
for accessing the form fields. We'll see in the next part, how to access these features from a function component using hooks like useEffect()
and useRef()
.
This is an a truncated example of the CustomersList
component which is available from this file:
import React, { Component } from 'react';
import CustomersService from './CustomersService';
const customersService = new CustomersService();
class CustomersList extends Component {
constructor(props) {
super(props);
this.state = {
customers: [],
nextPageURL: ''
};
/*...*/
}
componentDidMount() {
var self = this;
customersService.getCustomers().then(function (result){
self.setState({ customers: result.data, nextPageURL: result.nextlink})
});
}
handleDelete(e,pk){
}
nextPage(){
}
render() {
return (
<div className="customers--list">
<table className="table">
<-- Truncated -->
<tbody>
{this.state.customers.map( c =>
<tr key={c.pk}>
<!-- Truncated -->
</tr>)}
</tbody>
</table>
</div>
);
}
}
export default CustomersList;
This component extends React.Component
and use the this.state
object to maintain the state which contains the customers array and the nextPageURL
string which stores the link to the next page of data to fetch from the Django API. We also use the setState()
method to update the state once we get data from the server using the customersService.getCustomers()
method.
In the render()
method we loop through the this.state.customers
array and display our table rows.
Now we want to convert this class component to a function component and be able to maintain state in our function just like the example on top.
import React, { useState } from 'react';
import CustomersService from './CustomersService';
const customersService = new CustomersService();
function CustomersList(props){
const [customers, setCustomers] = useState([]);
const [nextPageURL, setNextPageURL] = useState('');
function handleDelete(e, pk){}
function nextPage(){}
return (
<div className="customers--list">
<table className="table">
<thead key="thead">
<tr>
<!-- Truncated -->
</tr>
</thead>
<tbody>
{customers.map( c =>
<tr key={c.pk}>
<!-- Truncated -->
</tr>)}
</tbody>
</table>
<button className="btn btn-primary" onClick= { nextPage }>Next</button>
</div>
);
}
The useState
hook returns an array with 2 elements. We use using array destructuring to assign names to the two elements. The first element is the initial value of the state, and the second element is a function to set the state; which you can call with a new value to set the related state.
Next, on the componentDidMount
life-cycle method which is fired when the component is mounted on the DOM we sent a request to the server, we subscribe to the Observable and then we call the setState
method
this.setState({ customers: result.data, nextPageURL: result.nextlink})
We can achieve this same behavior using the useEffect()
hook, setCustomers()
and setNextPageURL()
functions that we get from the useState()
hook.
useEffect()
is a built in React hook that's used to run side effects and it's equivalent to three life-cycle methods componentDidMount
, componentDidUpdate
and componentWillUnmount
. We'll see more about this hook in a separate tutorial.
Just below the useState()
hook in your component function, add the following code:
useEffect(() => {
customersService.getCustomers().then(function (result) {
setCustomers(result.data);
setNextPageURL(result.nextlink);
});
});
Finally, just add the implementations for handleDelete()
and nextPage()
in your CustomersList
function component:
function handleDelete(e, pk){
customersService.deleteCustomer({pk : pk}).then(()=>{
var newArr = customers.filter(function(obj) {
return obj.pk !== pk;
});
setCustomers(newArr);
});
}
function nextPage(){
customersService.getCustomersByURL(this.state.nextPageURL).then((result) => {
setCustomers(result.data);
setNextPageURL(result.nextlink);
});
}
Again, we simply use the setCustomers()
and setNextPageURL()
functions returned from destructuring the array returned by the useState()
hook.
Conclusion
In this article, we've learned how to utilize the useState
hook to maintain state inside React 18 functional components.
In the next tutorial, we'll look at another useful built-in hook called useEffect
which can be used to perform side effects in your function components.
-
Date: