Understanding React hooks
React
is one of, if not the most well-liked JavaScript
frameworks. An attribute called Hooks is part of React
. Due to their complexity, React
hooks might be confusing for beginners, who thus become stuck when they encounter them. In this article, we will discuss that.
What are React hooks?
React
hooks are JavaScript
functions that isolate the reusable part from a functional component.
Hooks are React
APIs
added to React
16.8. They made it possible for features previously only used in React
class components to come into React
functional components. They are functions that bring the power of React
class components to functional components. Hooks allow us to separate our code, manage side effects, and more.
Origin of React hooks
React
functional and class components had different purposes before hooks. The only time functional components were utilized was to render data to the user interface (UI). Only parent components, which were class, could receive and render props
for them. Functional components were unaware of the lifecycle and did not maintain an internal state.
Contrarily, class components keep track of a component’s internal state and provide lifecycle methods that let you do activities during each stage. For instance, after a component mounts, you can update the state due to user interaction and fetch data from an external API
. A class component keeps track of its internal state and lifetime, making all of this feasible. The term “smart components” was used to describe class components and is still used today.
Hooks
were added to solve some of the problems related to using React
class components, but some are not directly connected to React
but, rather, the way native JavaScript
classes are designed.
React hooks have a few rules, including:
- Hooks can only be called inside React function components.
- Hooks can only be called at the top level of a component.
- Hooks cannot be conditional
These rules are crucial; breaking them could make your hooks stop working.
Types of React hook
- useState
- useEffect
- useCallback
- useMemo
- useContext
- useRef
- useReducer
- useId
- useImperativeHandle
- useDebugValue
- useDeferredValue
- useInsertionEffect
- useLayoutEffect
- useSyncExternalStore
- useTransition
Above are the types of React
hooks we have, but we will be digesting the first four (4) out of the fifteen we have listed because those are the ones we are likely to use when working on a project.
useState
In some projects (websites, web apps, etc.), we need them to behave differently when the user inputs different values or opt-in for different options. useState
allows us to achieve that.
The React
useState
hook lets us keep track of the state while using the function component.
”State,” according to Oxford Languages, is the particular condition someone or something is in at a specific time. In React
, state refers to the value of a component’s property at a specific time.
By using useState,
we know when these values have changed from their initial state to another, and we can update our components according to those changes.
Let’s write some code to implement the useState
using a functional component named myFavouriteFood
.
import { useState } from "react";
function myFavouriteFood() {
const [food, setFood] = useState("fried rice");
return <h1> My favorite Food is {food}!</h1>;
}
We first imported the useState
into our module in the code above because we cannot use it unless we first import it into our module. useState
is defined before the return
keyword; this is to allow them to initialize before the function returns any value, as we can see in our code. We initialize our useState
at line 4 before returning a value at line 6. Some of us might need clarification about what we did in line 4. We destructured the useState
to get the return
value. The useState
function returns a list with two values as its output. The first value is the actual state we will be using, while the second is a function we will use to update this state. By destructuring them, we now get their values. The first value is assigned to the variable food, while the second is assigned to the variable setFood.
Updating our state
How do we update the value of food based on the user’s activity on the web app? To explain this, let’s say we have the multiple choice option and want to return different food as the user’s favorite when they choose one out of the multiple choices we provided.
Let’s update our code to add multiple choices.
import { useState } from "react";
function myFavouriteFood() {
const [food, setFood] = useState("");
const updateFood = (e) => setFood(e.target.value);
return (
<>
<h1> Select your favourite food </h1>
<input
type="radio"
id="Fried rice"
name="fav food"
onClick={updateFood}
value="Fried rice"
/>
<label for="Fried rice">Fried rice</label>
<br />
<input
type="radio"
id="Jollof rice"
name="fav food"
onClick={updateFood}
value="Jollof rice"
/>
<label for="Jollof rice">Jollof rice</label>
<br />
<input
type="radio"
id="fufu"
name="fav food"
onClick={updateFood}
value="Fufu and Egusi soup"
/>
<label for="fufu">Fufu and Egusi soup</label>
<br />
<p> My favorite Food is {food}!</p>
</>
);
}
In our code, we updated the value of food when the user clicked on a particular option using events. We achieved this by passing a callback function to the onClick
property. Inside the callback function, the updateFood
function, we updated the variable food by calling the setFood
function and passing the value of the option the user clicked as an argument. Take note that the second value that useState
returns, which is setFood
in our case, takes in one argument: the updated value of our state. To understand more about JavaScript
events, try reading this article All About JavaScript Events.
Although updating strings, integers, and boolean in useState
is simple, updating objects and arrays in useState
can be difficult due to how they behave. In React
, we are not allowed to mutate the values inside the useState
. What I’m trying to say is that the values that useState
holds shouldn’t be modified; instead, when useState
is updated, the values that are supplied to it should point to a new object, as is the default behavior of integer, string, and boolean. As a result, they are easier to update than arrays and objects in useState
. Updating objects and arrays in useState
, we must create a duplicate or new value that points to a new object.
Updating arrays in State
We use the spread method ...
to update arrays in the state. Depending on what we want to accomplish, we can also utilize the concat, map, filter, and slice methods. We use these methods to update arrays in the state since they return an array pointing to a new object. But in this instance, we’ll employ the spread strategy.
import { useState } from "react";
function myFavouriteFood() {
// Array as the initial value of useState
const [foodClicked, setFoodClicked] = useState([]);
const updateFood = (e) => {
// Updating arrays
const newFoodClicked = [...foodClicked, e.target.value];
setFoodClicked(newFoodClicked);
};
return (
<>
<h1> Select your favourite food </h1>
<input
type="radio"
id="FR"
name="fav food"
onClick={updateFood}
value="Fried rice"
/>
<label for="FR">Fried rice</label>
<br />
<input
type="radio"
id="JR"
name="fav food"
onClick={updateFood}
value="Jollof rice"
/>
<label for="JR">Jollof rice</label>
<br />
<input
type="radio"
id="fufu"
name="fav food"
onClick={updateFood}
value="Fufu and Egusi soup"
/>
<label for="fufu">Fufu and Egusi soup</label>
<br />
<hr />
<p> The list of Foods you have clicked are!</p>
{foodClicked.map((item) => (
<p>{item}</p>
))}
</>
);
}
Try running this code and see the output. You are expected to see the foods you have clicked.
Updating objects in State
The spread method ...
is used to update objects in State. The concat, map, and other methods we have stated in the array are not applicable because objects don’t support them.
import { useState } from "react";
function myFavouriteFood() {
// Object as the initial value of useState
const [foodData, setFoodData] = useState({
"Fried rice": 0,
"Jollof rice": 0,
"Fufu and Egusi soup": 0,
});
const updateFood = (e) => {
// Updating objects
foodData[e.target.value]++;
setFoodData({ ...foodData });
};
return (
<>
<h1> Select your favourite food </h1>
<input
type="radio"
id="FR"
name="fav food"
onClick={updateFood}
value="Fried rice"
/>
<label for="FR">Fried rice</label>
<br />
<input
type="radio"
id="JR"
name="fav food"
onClick={updateFood}
value="Jollof rice"
/>
<label for="JR">Jollof rice</label>
<br />
<input
type="radio"
id="fufu"
name="fav food"
onClick={updateFood}
value="Fufu and Egusi soup"
/>
<label for="fufu">Fufu and Egusi soup</label>
<br />
<hr />
<p>
{" "}
Foods and times you have clicked them:{" "}
{Object.keys(foodData).map((item) => (
<p>{`${item}: ${foodData[item]}`}</p>
))}
</p>
</>
);
}
Try running this code and see the output. You are expected to see the foods you have clicked and the number of times you have clicked them.
Session Replay for Developers
Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — an open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.
useEffect
As its name suggests, the useEffect
hook allows you to perform side effects in your components. It enables you to synchronize a component with an external system. In this scenario, external systems are those systems that are not managed by React
, such as some browser APIs or third-party libraries.
How useEffect
is triggered in React
depends on its dependencies array.useEffect
accepts two arguments. The second argument is optional.
useEffect(<function>, <dependency array>)
// second argument is optional.
The first argument passed to the useEffect
function is a callback function, and the second is an array popularly called a dependency array. A dependency array contains the values that your first argument (function) depends on. To use useEffect
, we need to import it into our module and initialize it.
import { useEffect, useState } from "react";
import fetchPost from "./post";
function PostDetails() {
const [post, setPost] = useState([]);
useEffect(() => {
const get_post = fetchPost({ details });
setPost(get_post);
});
}
Our useEffect
runs after our components are rendered. When our component is rendered, the post is fetched from the API,
and we update the post variable (state) with its value when it returns a value. If we run this code, we will figure out that the useEffect
is triggered continuously without stopping, making our component keep re-rendering. We might not notice that our component is being re-rendered because it shows the same content. But we can see the browser continuously fetching the post when we move to the network tab after right-clicking and clicking on inspect.
To prevent this, we need to add the dependency array. As I said, how useEffect
is triggered in React
depends on its dependency array. The dependency array tells React
either to re-run a useEffect
or not. The dependency array can be filled, empty, or not specified, and the state it is in makes it behave differently. How does this work?
Filled
If a dependency array is specified or filled, React
re-runs the useEffect
whenever any of the values inside the dependency array changes.
import { useEffect, useState } from "react";
import fetchPost from "./post";
function PostDetails() {
const [post, setPost] = useState([]);
const [clicked, setClicked] = useState(false);
useEffect(() => {
const get_post = fetchPost({ details });
setPost(get_post);
}, [clicked]); // Filled array.
}
When the value clicked is changed, React
re-run the useEffect
and re-renders the component.
Empty
This is when we provide the dependency array without adding any value inside the array. This tells React
to run the useEffect
when the component is rendered for the first time only. The useEffect
runs only ones which are when the component renders the first time.
import { useEffect, useState } from "react";
import fetchPost from "./post";
function PostDetails() {
const [post, setPost] = useState([]);
useEffect(() => {
const get_post = fetchPost({ details });
setPost(get_post);
}, []); // Empty array.
}
Not specified
This is when the dependency array is not specified at all. It tells React
to re-run the useEffect
whenever the component re-renders.
import { useEffect, useState } from "react";
import fetchPost from "./post";
function PostDetails() {
const [post, setPost] = useState([]);
useEffect(() => {
const get_post = fetchPost({ details });
setPost(get_post);
}); // No array.
}
React
will continue to re-run this useEffect
whenever the component re-renders. If you are not careful when using your useEffect
this way, you might end up defining a useEffect
that keeps re-running. How I address this issue is by setting a condition the function inside the useEffect
must pass before it can run.
useCallback
The React
useCallback
Hook returns a memoized callback function.
Memoization, according to Wikipedia In computing, is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again. This is what the React
useCallback
hook does, but instead of caching the value, the useCallback
hook caches the function itself. React
when re-rendering a component recreates all of its functions. This automatically decreases performance. useCallback
addresses this issue by telling React
whether to recreate its function or not when re-rendering a component.
This allows us to isolate resource-intensive functions so that they will not automatically run on every render. useCallback
accepts two arguments, just like useEffect
; the first is a function, while the second is an array known as a dependency array.
useCallback(<function>, <dependency array>)
The useCallback
hook only runs when one of its dependencies updates.
import { useState, useCallback } from "react";
function App() {
const [count, setCount] = useState(0);
const [todos, setTodos] = useState([]);
const increment = () => {
setCount((c) => (c += 1));
};
const addTodo = useCallback(() => {
setTodos((t) => [...t, "New Todo"]);
}, [todos]);
return (
<div>
<p> Todos {todos} </p>
<p> Count: {count}</p>
<button style={{ marginRight: "20px" }} onClick={addTodo}>
{" "}
add Todo
</button>
<button onClick={increment}>+</button>
</div>
);
}
When we click the +
button, the useCallback
doesn’t run because its dependency todos
was not changed. The useCallback
will run when we click on the add Todo button, which updates the todos
state.
useMemo
The React
useMemo
Hook returns a memoized value.
The useMemo
hook is quite similar to the useCallback
hook; however, instead of caching functions as in the case of useCallback
, we cache and return the outcome of an expensive calculation in useMemo
.
You can prevent expensive, resource-intensive functions from running unnecessarily by using the useMemo
Hook. Like useCallback
, useMemo
also takes two arguments: the first is a function, and the second is an array called a dependency array.
useMemo(<function>, <dependency array>)
Defining an expensive function inside a component will decrease the performance of the component.
import { useState, useCallback } from "react";
function App() {
const [count, setCount] = useState(0);
const [todos, setTodos] = useState([]);
const increment = () => {
setCount((c) => (c += 1));
};
function expensiveCalculation(int) {
for (let i = 0; i < 1000000000; i++) {
int += 1;
}
return int;
}
const calculate = expensiveCalculation(count);
const addTodo = useCallback(() => {
setTodos((t) => [...t, "New Todo"]);
}, [todos]);
return (
<div>
<p> Todos {todos} </p>
<p> Count: {count}</p>
<button style={{ marginRight: "20px" }} onClick={addTodo}>
{" "}
add Todo
</button>
<button onClick={increment}>+</button>
<hr />
<p>Expensive calculation</p>
<span>{calculate}</span>
</div>
);
}
It will take some time for the component to update when we run this code and hit the add Todo or +
buttons. This demonstrates how this expensive function keeps executing during every re-render, lowering the component’s performance.
However, applying useMemo
will prevent our component from acting this way.
import { useState, useMemo, useCallback } from "react";
function App() {
const [count, setCount] = useState(0);
const [todos, setTodos] = useState([]);
const increment = () => {
setCount((c) => (c += 1));
};
function expensiveCalculation(int) {
for (let i = 0; i < 1000000000; i++) {
int += 1;
}
return int;
}
const calculate = useMemo(() => expensiveCalculation(count), [count]);
const addTodo = useCallback(() => {
setTodos((t) => [...t, "New Todo"]);
}, [todos]);
return (
<div>
<p> Todos {todos} </p>
<p> Count: {count}</p>
<button style={{ marginRight: "20px" }} onClick={addTodo}>
{" "}
add Todo
</button>
<button onClick={increment}>+</button>
<hr />
<p>Expensive calculation</p>
<span>{calculate}</span>
</div>
);
}
This time, when we run this code, clicking the +
button causes our component to update gradually, but clicking the add Todo
button causes it to update instantly. This is so because, like useCallback
, the useMemo
hook only executes when one of its dependencies changes, in this case, only when the +
button is clicked to update the count variable.
Conclusion
We understand that React
hooks are simply JavaScript
functions. It allows us to implement some functionality that was only in the React
class components in the React
functional components. We also saw that the useEffect
hook’s re-rendering depends on its dependency array, useCallback
and useMemo
hooks will only run when any value in their dependency array changes, and useState
helps us keep track of our component state.