Understanding the REACT useState and useEffect HOOKS
The two most common React hooks.
Table of contents
The inspiration
When I started learn learning React, at first I had some troubles in getting to grasps how these hooks work until I finally grasped it after using it to build some applications and studying documentation. The React useState
and useEffect
hooks are very important hooks. TheuseState
hook is the most used React hook. By the end of this article you will understand how the useState
and useEffect
hooks work and how to use them in React, buckle up and lets dive in 😎🎉.
INTRODUCTION
I assume before coming to find this article you already know what React is, just incase you don't, React is a free and open-source front-end JavaScript library for making/building user interfaces. It is currently being maintained by Meta.
WHAT ARE HOOKS ?
Hooks are functions that lets you “hook into” React state and lifecycle features from function components, and make use of React features.
Hooks don’t work inside classes but rather they let you use React without classes.
Hooks give power to React functional components, making it possible to develop an entire application with it.
The Hooks Convention And Rules
Before delving into hooks, it could be helpful to take a look at the convention and rules that apply to them. Here are some of the rules that apply to hooks.
The naming convention of hooks should start with the prefix
use
. So, we can haveuseState
,useEffect
,useReducer
etc. If you are using modern code editors like Atom and VSCode, the ESLint plugin could be a very useful feature for React hooks. The plugin provides useful warnings and hints on the best practices.Hooks must be called at the top level of a component, before the return statement. They can’t be called inside a conditional statement, loop, or nested functions.
Hooks must be called from a React function (inside a React component or another hook). It shouldn’t be called from a Vanilla JS function.
Now lets get into the main reason for this article :
1) THE useState
HOOK
The useState
hook is the most basic and useful React hook. Like other built-in hooks, this hook must be imported from 'react'
to be used in our applications.
import {useState} from 'react'
To initialize the state, we must declare both the state and its updater function and pass an initial value.
const [state, updaterFn] = useState('')
Furthermore, we are free to call our state and updater function whatever we want but by convention, the first element of the array will be our state while the second element will be the updater function. It is a common practice to prefix our updater function with the prefix set
followed by the name of our state in camel case form.
For instance, let’s set a state to hold count values.
const [count, setCount] = useState(0)
Notice that the initial value of our count state is set to "0" and not an empty string. In other words, we can initialize our state to any kind of JavaScript variables, namely number, string, boolean, array and objects. There is a clear difference between setting state with the useState
hook and class-based component states. It is noteworthy that the useState
hook returns an array, also known as state variables and in the example above, we destructured the array into state and the updater function.
More on useState
hooks and how they differ from classes.
** 🚀 What does calling useState
do ?** It declares a “state variable”. Our variable is called count
but we could call it anything else, like banana
. This is a way to “preserve” some values between the function calls, useState
is a new way to use the exact same capabilities that this.state
provides in a classes. Normally, variables “disappear” when the function exits but state variables are preserved by React.
** 🚀 What do we pass to useState
as an argument?** The only argument to the useState()
Hook is the initial state. Unlike with classes, the state doesn’t have to be an object. We can keep a number or a string if that’s all we need. In our example, we just want a number for how many times the user clicked, so pass 0 as initial state for our variable. If we wanted to store two different values in state, we would call useState()
twice.
** 🚀 What does useState
return?** It returns a pair of values: the current state and a function that updates it. This is why we write const [count, setCount] = useState()
. This is similar to this.state.count
and this.setState
in a classes, except you get them in a pair. If you’re not familiar with classes do not bother you can always go to the React's official website to read more.
Now that we know what the useState Hook does, our example below should make more sense:
import React, { useState } from 'react';
function Example() {
// Declare a new state variable, which we will call "count"
const [count, setCount] = useState(0);
We declared a state variable called count, and set it to 0. React will remember its current value between re-renders, and provide the most recent one to our function. If we want to update the current count, we can call setCount
.
Rerendering Components : Setting state with the useState
hook causes the corresponding component to rerender. However, this only happens if React detects a difference between the previous or old state and the new state.
📌 Note
You might be wondering why is useState
not named createState
instead?
“create” wouldn’t be quite accurate because the state is only created the first time our component is being rendered. During the next renders, useState
gives us the current state. Otherwise it wouldn’t be “state” at all! There’s also a reason why Hook names always start with use
Learn more.
Bonus Tip : What Do the Square Brackets '[]' Mean?
You might have noticed the square brackets when we declare a state variable what do they really mean why are we not using normal brackets ?
const [count, setCount] = useState(0);
The names on the left aren’t a part of the React API. You can name your own state variables:
const [fruit, setFruit] = useState('banana');
This JavaScript syntax is called “array destructuring”. It means that we’re making two new variables fruit
and setFruit
, where fruit
is set to the first value returned by useState
, and setFruit
is the second. It is equivalent to this code:
var fruitStateVariable = useState('banana'); // Returns a pair
var fruit = fruitStateVariable[0]; // First item in a pair
var setFruit = fruitStateVariable[1]; // Second item in a pair
When we declare a state variable with useState
, it returns a pair — an array with two items. The first item is the current value, and the second item is a function that lets us update it.
2) THE useEffect
HOOK
The
useEffect
is another important React hook used in most projects. TheuseEffect
provides us an opportunity to write imperative code that may have side effects on the application. Examples of such effects include logging, subscriptions, mutations, etc. It does a similar thing to the class-based component’scomponentDidMount
,componentWillUnmount
, andcomponentDidUpdate
lifecycle methods but that is not our concern in this article.The user can decide when the
useEffect
will run, however, if when the effect is to be ran is not set, the side effects will run on every rendering or rerendering.
Consider the example below👇🏾:
import {useState, useEffect} from 'react'
const App = () =>{
const [count, setCount] = useState(0)
useEffect(() =>{
console.log(count)
})
return(
<div>
...
</div>
)
}
In the code above, we simply logged count in the useEffect
. This will run after every render of the component.
Sometimes, we may want to run the hook once (on the mount) in our component. We can achieve this by providing a second parameter to useEffect
hook.
import {useState, useEffect} from 'react'
const App = () =>{
const [count, setCount] = useState(0)
useEffect(() =>{
setCount(count + 1)
}, [])
return(
<div>
<h1>{count}</h1>
...
</div>
)
}
- The
useEffect
hook has two parameters, the first parameter is the function we want to run while the second parameter is called the dependencies array . If the second parameter(dependencies array) is not provided, the hook will run continuously, which is not what we want.
By passing an empty square bracket to the hook’s second parameter, we instruct React to run the useEffect
hook only once, which is upon mount. This will display the value 1 in the h1 tag because the count will be updated once, from 0 to 1, when the component mounts.
We could also make our side effect run whenever some dependent values change. This can be done by passing these values in the list of dependencies.
For instance, we could make the useEffect
to run whenever count changes as follows.
import { useState, useEffect } from "react";
const App = () => {
const [count, setCount] = useState(0);
useEffect(() => {
console.log(count);
}, [count]);
return (
<div>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
};
export default App;
The useEffect
above will run when either of these two conditions is met.
On mount — after the component is rendered or,
When the value of
count
changes.
On mount, the console.log
expression will run and log count
to 0. Once the count
is updated, the second condition is met, so the useEffect
runs again, this will continue whenever the button is clicked.
Once we provide the second argument to useEffect
, it is expected that we pass all the dependencies to it. If you have ESLINT installed, it will show a lint error if any dependency is not passed to the parameter list. This could also make the side effect behave unexpectedly, especially if it depends on the parameters that are not passed.
Cleaning up the Effect
useEffect
hook also allows us to clean up resources before the component unmounts. This may be necessary to prevent memory leaks and make the application more efficient. To do this, we’d return the clean-up function at the end of the hook.
useEffect(() => {
console.log('mounted')
return () => console.log('unmounting... clean up here')
})
The useEffect
hook above will log mounted when the component is mounted. Unmounting… clean up here will be logged when the component unmounts. This can happen when the component is removed from the UI.
The clean-up process typically follows the form below.
useEffect(() => {
//The effect we intend to make
effect
//We then return the clean up
return () => the cleanup/unsubscription
})
While you may not find so many use cases for useEffect
subscriptions, it is useful when dealing with subscriptions and timers. Particularly, when dealing with web sockets, you may need to unsubscribe from the network to save resources and improve performance when the component unmounts.
Fetching and refetching data with useEffect
hook.
One of the commonest use cases of the useEffect
hook is fetching and refetching data from an API.
To illustrate this, we’ll use fake user data I created from JSONPlaceholder to fetch data with the useEffect hook.
import { useEffect, useState } from "react";
import axios from "axios";
export default function App() {
const [users, setUsers] = useState([]);
const endPoint =
"https://my-json-server.typicode.com/ifeanyidike/jsondata/users";
useEffect(() => {
const fetchUsers = async () => {
const { data } = await axios.get(endPoint);
setUsers(data);
};
fetchUsers();
}, []);
return (
<div className="App">
{users.map((user) => (
<div>
<h2>{user.name}</h2>
<p>Occupation: {user.job}</p>
<p>Sex: {user.sex}</p>
</div>
))}
</div>
);
}
In the code above, we created a users
state using the useState
hook. Then we fetched data from an API using Axios. This is an asynchronous process, and so we used the async/await function, we could have also used the dot then the syntax. Since we fetched a list of users
, we simply mapped through it to display the data.
Notice that we passed an empty parameter to the hook. This ensures that it is called just once when the component mounts.
We can also refetch the data when some conditions change. We’ll show this in the code below.
import { useEffect, useState } from "react";
import axios from "axios";
export default function App() {
const [userIDs, setUserIDs] = useState([]);
const [user, setUser] = useState({});
const [currentID, setCurrentID] = useState(1);
const endPoint =
"https://my-json-server.typicode.com/ifeanyidike/userdata/users";
useEffect(() => {
axios.get(endPoint).then(({ data }) => setUserIDs(data));
}, []);
useEffect(() => {
const fetchUserIDs = async () => {
const { data } = await axios.get(`${endPoint}/${currentID}`});
setUser(data);
};
fetchUserIDs();
}, [currentID]);
const moveToNextUser = () => {
setCurrentID((prevId) => (prevId < userIDs.length ? prevId + 1 : prevId));
};
const moveToPrevUser = () => {
setCurrentID((prevId) => (prevId === 1 ? prevId : prevId - 1));
};
return (
<div className="App">
<div>
<h2>{user.name}</h2>
<p>Occupation: {user.job}</p>
<p>Sex: {user.sex}</p>
</div>
<button onClick={moveToPrevUser}>Prev</button>
<button onClick={moveToNextUser}>Next</button>
</div>
);
}
Here we created two useEffect
hooks. In the first one, we used the dot then syntax to get all users from our API. This is necessary to determine the number of users.
We then created another useEffect
hook to get a user based on the id. This useEffect
will refetch the data whenever the id changes. To ensure this, we passed the id in the dependency list.
Next, we created functions to update the value of our id whenever the buttons are clicked. Once the value of the id changes, the useEffect
will run again and refetch the data.
If we want, we can even clean up or cancel the promise-based token in Axios, we could do that with the clean-up method discussed above.
Conclusion
In other to learn any programming concept, it is best to go hands on and practice using these concepts in building real world projects this will allow you understand indepth how things really work.
Hope you have learnt allot happy coding 😎🚀🎉.