React Hooks Fundamentals for Beginners: How to Use useState and useEffect hooks 2023
Chidera Humphrey
Posted on June 24, 2023
Table of contents
- What are React hooks
- Managing state with the useState hook
- Using the useEffect hook to handle side-effects in React apps
- Refactoring Class Components to Functional Components with Hooks
- Benefits of using React hooks
- Concluding thoughts
What are react hooks
React hooks are functions that let you use different React features from your functional components. This means that you can manage state, make API calls, and other things a class-based component can do from functional components.
There are different hooks in React which let you do different things.
-
useState
hook lets you create and manage state. -
useEffect
hook lets you handle side-effects in your React application -
useContext
hook lets you share state without prop drilling, awesome right? -
useMemo
hook: lets you memoize your react components -
useCallback
hook is used to memoize functions, optimizing performance by preventing unnecessary re-rendering of components that depend on those functions. -
useReducer
hook: this hook is similar to the useState hook but handles complex state more efficiently. -
useRef
hook provides a way to create mutable references to elements or values that persist across renders.
This is not an exhaustive list but these are the hooks you will use 99% of the time.
Managing state with useState hook
The useState hook lets you add and manage state in a React component. It’s equivalent to this.state = {}
in class components.
To use the useState hook, call it at the top-level of your component:
const [state, setState] = useState(initialState);
It returns an array of exactly two values: current state and a function for updating that state.
Let’s look at them closely.
state
: this is the current state. When your component first renders, the state equals the initialState
.
setState
: this is a function for updating the state and trigger a re-render.
initialState
: the value you want the state to be initially. It can be of any valid JavaScript value.
Note: The setState
function only updates the state variable for the next render. If you read the state variable after calling the setState
function, you will still get the old value that was on the screen before your call.
Also, React will skip re-rendering of your component if the value you provide is identical to the current state.
Usage
To use the useState hook, you call it at the top level of your component.
import {useState} from “react”
function (){
const [state, setState] = useState(‘’);
}
Basic applications of useState
Updating a text field
In this simple example, the firstName
and lastName
state variables hold a string. When you type into the input fields, the handleFirstName
and handleLastName
updater functions read your inputs in the DOM and updates the UI accordingly.
import { useState } from 'react';
export default function MyInput() {
const [firstName, setFirstName] = useState(‘’);
const [lastName, setLastName] = useState(‘’);
function handleFirstName(e) {
setFirstName(e.target.value);
}
function handleLastName(e) {
setLastName(e.target.value);
}
return (
<>
<input value={firstName} onChange={handleFirstName} />
<input value={lastName} onChange={handleLastName} />
<p>{firstName} {lastName} is typing.</p>
</>
);
}
Updating objects and arrays in state
When updating arrays or objects in state, you should not directly mutate the state.
Here’s what I mean 👇
state.age = 42; // you should not directly mutate state.
Instead, you should replace the old state with a new one.
SetState({
...old state,
age: 42
})
This is also applicable to arrays.
state.push(“Banana”) // not recommended
setState([...prevState, “Banana”])
Example of object in state
In this example, the employee state variable holds an object. Each input field has a change handler that calls the setEmployee
function to update the state. The spread syntax (...)
to make sure state is replaced and not mutated.
import { useState } from 'react';
export default function Employee() {
const [employee, setEmployee] = useState({
firstName: “”,
lastName: “”,
job: '',
});
return (
<>
<label>
First name:
<input
value={employee.firstName}
onChange={e => {
setEmployee({
...employee,
firstName: e.target.value
});
}}
/>
</label>
<label>
Last name:
<input
value={employee.lastName}
onChange={e => {
setEmployee({
...employee,
lastName: e.target.value
});
}}
/>
</label>
<label>
Job:
<input
value={employee.job}
onChange={e => {
setEmployee({
...employee,
job: e.target.value
});
}}
/>
</label>
<p>
{employee.firstName}{' '}
{employee.lastName}{' '}
({employee.job})
</p>
</>
);
}
Example of array in state
In this example, the fruits state variable holds an array. Each button handler that calls the setFruits
function to update the state. The spread syntax (...) to make sure state is replaced and not mutated.
import { useState } from 'react';
import AddTodo from './AddTodo.js';
import TaskList from './TaskList.js';
let nextId = 3;
const initialTodos = [
{ id: 0, title: "'Buy milk', done: true },"
{ id: 1, title: "'Eat tacos', done: false },"
{ id: 2, title: "'Brew tea', done: false },"
];
export default function TaskApp() {
const [todos, setTodos] = useState(initialTodos);
function handleAddTodo(title) {
setTodos([
...todos,
{
id: nextId++,
title: "title,"
done: false
}
]);
}
function handleChangeTodo(nextTodo) {
setTodos(todos.map(t => {
if (t.id === nextTodo.id) {
return nextTodo;
} else {
return t;
}
}));
}
function handleDeleteTodo(todoId) {
setTodos(
todos.filter(t => t.id !== todoId)
);
}
return (
<>
<AddTodo
onAddTodo={handleAddTodo}
/>
<TaskList
todos={todos}
onChangeTodo={handleChangeTodo}
onDeleteTodo={handleDeleteTodo}
/>
</>
);
}
Handling side effects with useEffect
hook
The useEffect
hook is used to handle side effects in React. Side effects mean operations not controlled by React. Common side effects include:
- Making calls to a third-party API
- Interacting with the browser’s local storage
- Communicating with the DOM
- Implementing certain libraries such as: animation libraries.
To use the useEffect
hook, declare it at the top-level of your component
import React, { useEffect } from 'react';
function ExampleComponent() {
useEffect(() => {
// Side effect code goes here
// Clean up the effect (optional)
return () => {
// Cleanup code goes here
};
}, []);
return (
// Component JSX goes here
);
}
export default ExampleComponent;
The useEffect
hook takes two arguments:
The first argument is a callback function that contains the code for the side effect. This is where you can perform API calls, manipulate the DOM, interact with browser storage, or integrate with external libraries.
The second argument is the dependency array, which determines when the effect should run. By passing an empty array []
, the effect will only run once, after the initial render.
If you want the effect to run again when certain dependencies change, you can include them in the dependency array.
Now that you have a good understanding of the useEffect
hook and its purpose in handling side effects in React, let's look at some common use cases and see how it can be used in practice.
Making calls to third-party APIs
In this example, we'll look at how to use the useEffect
hook in React to make calls to third-party APIs. Making API calls is something you will do more often as a front-end developer, and the useEffect
hook provides a convenient way to handle such asynchronous operations.
We are going to use the IMDB API to retrieve details of a film.
Let's dive into the code to see how it's done.
import React, { useEffect, useState } from 'react';
function FilmDetails() {
const [film, setFilm] = useState(null);
useEffect(() => {
// Function to fetch film details from the IMDB API
const fetchFilmDetails = async () => {
try {
const response = await fetch('https://api.example.com/films/123');
const data = await response.json();
setFilm(data);
} catch (error) {
console.error('Error fetching film details:', error);
}
};
fetchFilmDetails();
// Cleanup function
return () => {
// Cleanup code goes here
// This will be executed when the component is unmounted or when the dependency changes
};
}, []);
if (!film) {
return <p>Loading film details...</p>;
}
return (
<div>
<h2>{film.title}</h2>
<p>{film.description}</p>
{/* Render other film details */}
</div>
);
}
export default FilmDetails;
In this example, we have a functional component, FilmDetails
. We also use the useState
hook to manage the film
state which is initially set to null
.
Inside the useEffect
hook, we define an asynchronous function to fetch data from the IMDB API.
If the API call succeeds, we update the film
state with the retrieved data using setFilm
.
If an error occurs during the API call, we log the error message to the console within the catch
block.
We passed an empty dependency array as the second argument. This means that the effect will run only once—on initial render.
For better user experience, we have added a loading state in case the film data is yet available. Once it is available, we render the component JSX.
Finally, the return
statement inside the useEffect
returns a clean-up function, which will be executed when the component is unmounted or when the dependency changes. You can add any necessary clean-up code, such as canceling ongoing requests or removing event listeners, inside the clean-up function.
If the API call was successful, you should see the fetched film details displayed in your browser.
Interacting with the browser's local storage
In this example, we are going to reading and writing to the browser's local storage.
The browser's local storage is used for persisting data.
Let's dive into the code.
import React, { useEffect, useState } from 'react';
function LocalStorageExample() {
const [name, setName] = useState('');
useEffect(() => {
// Read from local storage
const storedName = localStorage.getItem('name');
if (storedName) {
setName(storedName);
}
}, []);
const handleNameChange = (e) => {
const newName = e.target.value;
setName(newName);
// Write to local storage
localStorage.setItem('name', newName);
};
return (
<div>
<h2>Hello, {name || 'stranger'}!</h2>
<input type="text" value={name} onChange={handleNameChange} />
</div>
);
}
export default LocalStorageExample;
In this example, we define a functional component, LocalStorageExample
. We use the useState
hook to manage the name
state.
We use the useEffect
hook to interact with the local storage. First, we read from the local storage using the getItem()
method. If name
key exists, we set the name
state to the value using the setName
function.
Next, we define a handleNameChange
function that is triggered when the input
's value changes. It updates the name state with the new value and uses localStorage.setItem()
to write the new value to the local storage, associating it with the key 'name'.
The component renders a heading that displays the greeting with the current name (or stranger
if no name is set), and an input field where the user can enter a new name.
Note: This code will not work in server-side as the localStorage only exists in the browser.
Refactoring Class Components to Functional Components with Hooks
Hooks were only introduced in React 16. This means that if you have been using React before then, you will most likely be using class components for tracking and managing data, as well as fetching and sending data.
In this section, I am going to show you how to refactor your class components to functional components using hooks.
Refactoring State Management: From Class Components to Functional Components
To refactor class components with state to functional components with state, we convert the class component's state management using this.state
and this.setState
to functional components utilizing the useState hook.
Class component
import React, { Component } from 'react';
class ClassComponentWithState extends Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
incrementCount = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.incrementCount}>Increment</button>
</div>
);
}
}
export default ClassComponentWithState;
Corresponding functional component
import React, { useState } from 'react';
function FunctionalComponentWithState() {
const [count, setCount] = useState(0);
const incrementCount = () => {
setCount(count + 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={incrementCount}>Increment</button>
</div>
);
}
export default FunctionalComponentWithState;
We converted the class component to functional component by replacing this.state
and this.setState
with the useState
hook. As explained above, the useState
hook returns an array with exactly two values: current state and an updated function.
Refactoring Data Fetching: From Class Components to Functional Components
To refactor data fetching from class components to functional components, we convert the class component's approach, which typically uses lifecycle methods like componentDidMount
and componentDidUpdate
, to functional components utilizing the useEffect
hook for handling data fetching and updating component state.
Class component with data fetching
import React, { Component } from 'react';
class ClassComponentWithDataFetching extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
updateCount: 0,
};
}
componentDidMount() {
// Data fetching
this.fetchData();
}
componentDidUpdate(prevProps, prevState) {
// Check if updateCount has changed
if (prevState.updateCount !== this.state.updateCount) {
this.fetchData();
}
}
fetchData = () => {
// Data fetching
fetch('https://api.example.com/data')
.then((response) => response.json())
.then((jsonData) => this.setState({ data: jsonData }))
.catch((error) => console.error('Error fetching data:', error));
};
handleUpdate = () => {
this.setState((prevState) => ({
updateCount: prevState.updateCount + 1,
}));
};
render() {
return (
<div>
{this.state.data.map((item) => (
<p key={item.id}>{item.name}</p>
))}
<button onClick={this.handleUpdate}>Update</button>
</div>
);
}
}
export default ClassComponentWithDataFetching
Functional component with data fetching
import React, { useEffect, useState } from 'react';
function FunctionalComponentWithDataFetching() {
const [data, setData] = useState([]);
const [updateCount, setUpdateCount] = useState(0);
useEffect(() => {
// Data fetching
const fetchData = async () => {
try {
const response = await fetch('https://api.example.com/data');
const jsonData = await response.json();
setData(jsonData);
} catch (error) {
console.error('Error fetching data:', error);
}
};
fetchData();
}, [updateCount]); // Update when updateCount changes
// Simulating component update triggering the useEffect
const handleUpdate = () => {
setUpdateCount((prevCount) => prevCount + 1);
};
return (
<div>
{data.map((item) => (
<p key={item.id}>{item.name}</p>
))}
<button onClick={handleUpdate}>Update</button>
</div>
);
}
export default FunctionalComponentWithDataFetching;
We converted the class component by replacing the componentDidMount
andcomponentDidUpdate
with the useEffect
hook. The dependency array helps us mimick the two lifecycle methods.
Benefits of Using the React hooks
Using React hooks provides several benefits in developing React applications:
Simplicity
Hooks enable functional components to have their own state and lifecycle methods. This makes it easier to read, write, and understand code without dealing with the complexity of class components.Reusability
Hooks promote code reuse. Custom hooks allow you to encapsulate logic and stateful behavior into reusable functions that you used across multiple components in your application. This promotes modular and maintainable code.Enhanced Functional Components
Hooks enable functional components to have state and side effects, previously only available in class components. You can manage component state using theuseState
hook and handle side effects with theuseEffect
hook, making functional components more powerful and versatile.Improved Performance
Hooks optimize performance by reducing unnecessary re-renders. With theuseMemo
anduseCallback
hooks, you can memoize values and functions respectively, preventing unnecessary computations or re-rendering of components.Easier Testing
Hooks promote functional programming concepts which makes testing easier, as the logic is separated into small, testable functions. This allows you to test components in isolation.Migration from Class Components
Hooks simplify the process of migrating from class components to functional components. Existing class components can be refactored to functional components using hooks, retaining the functionality while improving code readability and maintainability.Community Support
Hooks have gained widespread adoption and support from the React community. This means that you can find ample resources, documentation, and community-driven libraries to assist in learning and using hooks effectively.
Concluding Thoughts
React hooks are a powerful feature that allows developers to utilize different React functionalities within functional components. They enable state management, handling side effects, and refactoring class components to functional components.
The useState hook is used for managing state, while the useEffect hook is used for handling side effects such as making API calls. Hooks provide benefits such as code simplicity, reusability, and eliminating the need for class components.
Overall, React hooks offer an efficient and effective way to enhance the functionality and development experience in React applications.
Follow me on Twitter and LinkedIn for more content and updates.
Posted on June 24, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.