React hooks are relatively new in the seasoned developer’s world. This brings with it some complexities as they are a re-design of the solutions to the same old existing problems.
In the case of the useEffect hook clarity and understandability are among the complexities faced by most programmers. In this blog, I will expound on this hook more clearly, its applications as well as how to use it effectively.
Introduction
The useEffect hook allows you to synchronize a functional component with an external system or in simpler terms the ‘outside world’.
Our functional components in some instances may be required to reach beyond their scope after rendering and perform actions in the outside world, for example when making an API call. These instances are referred to as side effects and are quite unpredictable, making them hard to manage.
The useEffect allows you to manage these side effects in a declarative and predictable way in our functional components. Other examples of side effects include:
Manually changing the DOM
When using a timer; setTimeout and setInterval
Subscribing to events
It is however advisable to know when it is appropriate to use the useEffect hook. The idea of using it is to execute code that needs to happen during the lifecycle of the component instead of on specific user interactions or DOM events. This means it should not be applied in place of event handlers.
Syntax and Usage
Similar to other React hooks to use the useEffect hook, you should first import it from React:
import { useEffect } from 'react';
useEffect takes 2 arguments:
The effect function: This is the function containing the code that handles the side effect. By default, It will be executed after every render of the component.
The dependency array (optional): As the name suggests it contains values (props or state variables) which the effect function depends on. If the values change the effect is re-run again even though rendering is complete. When this array is omitted, the effect will re-run every time the component is rendered. When the array is empty the effect runs only once after initial rendering.
useEffect(() => {
//function to be executed
},[]) //empty dependency array
Let’s put it all together in a simple example:
- Create a new functional component, PostList :
import React, { useState, useEffect } from 'react';
const PostList = () => {
const [posts, setPosts] = useState([]);
useEffect(() => {
// Fetch data from the JSONPlaceholder API
fetch('https://jsonplaceholder.typicode.com/posts')
.then(response => response.json())
.then(data => setPosts(data));
}, []); // Empty dependency array to run the effect only once
return (
<div>
<h1>Posts</h1>
<ul>
{posts.map(post => (
<li key={post.id}>
<h2>{post.title}</h2>
<p>{post.body}</p>
</li>
))}
</ul>
</div>
);
};
The PostList component contains one state variable ‘posts’ whose value is updated using useEffect which fetches a list of posts from the JSONPlaceholder API.
The data is then mapped and displayed in an unordered list using the map function.
JSONPlaceholder is a fake online REST API for testing and prototyping. It provides a variety of data, including posts, users, comments, and more.
- The component needs to be attached to the root component
App
:
import React from 'react';
const App = () => { return ( <div className="App"> <PostList /> </div> ); }
export default App;
Output
In this instance, useEffect fetches data from an external API and passes it on to be loaded to the page.
Cleaning up
Recall that, the useEffect
hook is often used for tasks such as setting up subscriptions, timers, or modifying the DOM, it’s important to properly clean up after these tasks to avoid:
memory leaks
unnecessary processing
or to save on resources.
You can think of it as a way of putting an end to side effects that don’t need to be executed anymore.
For instance, say a componentOne
makes an API request for some data, but while making this asynchronous request, componentOne
is removed from the DOM (unmounted). There is no need to complete the asynchronous request hence a cleanup method can be added to cleanup the asynchronous request from completing.
A common indicator that your useEffect may require a cleanup function is the error:
Warning: Can't perform a React state update on an unmounted component.
This is a no-op, but it indicates a memory leak in your application.
To fix, cancel all subscriptions and asynchronous tasks
in a useEffect cleanup function.
useEffect is built in such a way that we can return a function inside it and this return function is where the cleanup happens.
An example of how we can clean up an API request on unmount as described for ‘componentOne’, could work as follows:
import React, { useState, useEffect } from "react";
const componentOne = () => {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
// Create a controller to cancel the fetch request
const controller = new AbortController();
const signal = controller.signal;
// Fetch data from an API
fetch("https://example.com/api/data", { signal })
.then((response) => response.json())
.then((data) => setData(data))
.catch((error) => setError(error));
// Return a function that cancels the fetch request
return () => {
controller.abort();
};
}, []); // Run only once when the component mounts
// Render the data or error
if (error) {
return <div>Error: {error.message}</div>;
} else if (data) {
return <div>Data: {JSON.stringify(data)}</div>;
} else {
return <div>Loading...</div>;
}
}import React, { useState, useEffect } from “react”;
When ‘componentOne’ unmounts and the fetch request has not been completed, the request is canceled.
This is just one example of side effects that need to be cleaned up in React. There are multiple others, whose clean-up functions vary depending on the specific implementation being undertaken in the useEffect.
Conclusion
The useEffect hook elevates the efficiency and interactivity of our apps by allowing us to better manage side effects. An understanding of it thus guarantees we can handle the challenges of asynchronous programming and craft applications that seamlessly marry functionality with user experience. Happy Coding!