Side-Effect & useEffect in React
What are side effects?
In React, “side effects” refer to any operations or behaviors that occur in a component after rendering, and that don’t directly impact the current component render cycle. These side effects can include tasks such as data fetching, subscriptions, manually changing the DOM, or other interactions with the outside world. One common use case for handling a side effect is making an asynchronous data request, such as fetching data from an API.
Fetching data from an API is a standard asynchronous operation in a web application. Let’s explore how to achieve this in a React project. When you begin writing code in React to fetch data from an API, you may encounter an issue where your application enters into an infinite rendering loop. This occurs because updating the state triggers a re-render, leading to the execution of the same code again.
import React, { useState, useEffect } from "react";
function App() {
const [studentList, setStudentList] = useState([]);
const api = "https://hp-api.onrender.com/api/characters/house/gryffindor";
//Fetching data from the API
fetch(api)
.then((response) => response.json())
.then((data) => {
setStudentList(data);
})
.catch((error) => {
console.log("Error while fetcing data: ", error);
});
return (
<>
<h1>List of students in Howarts</h1>
{studentList.map((student) => (
<>
<h4>{student.name}</h4>
</>
))}
</>
);
}
The above code will lead to an infinite rendering of our application. So what I am trying to achieve here is, I tried to fetching data from an API and once I will the get the data I am updating the state and once a state is updated in the a component a re-render will happen. When the component will re-render same code will execute again which will lead my application to infinite re-render. Now let’s discuss how to handle these kind of scenarios in React.
Addressing Infinite Rendering with useEffect()
To handle scenarios like the one described above, React provides a powerful hook called useEffect
. useEffect
is a hook in React that allows you to perform side effects in function components. useEffect
function takes two arguments: a function containing the code for the side effect, and an optional array of dependencies. Let’s break down both the arguments:
- Effect function: The first argument of
useEffect
is a function containing the code for the side effect. This function, commonly referred to as the "effect function," is executed after the component renders for the first time and after every subsequent render. - Dependency array: The second argument of
useEffect
is an optional array of dependencies. It specifies the values (variables or state) that the effect function depends on. When any of the dependencies change between renders, the effect function will be re-executed.
The Fix
Here’s how to fix the infinite rendering issue using useEffect
:
import React, { useState, useEffect } from "react";
function App() {
const [studentList, setStudentList] = useState([]);
const api = "https://hp-api.onrender.com/api/characters/house/gryffindor";
//Getting data from the
useEffect(() => {
fetch(api)
.then((response) => response.json())
.then((data) => {
setStudentList(data);
})
.catch((error) => {
console.log("Error while fetcing data: ", error);
});
}, []);
return (
<>
<h1>List of students in Howarts</h1>
{studentList.map((student) => (
<div key={student.id}>
<h4>{student.name}</h4>
</div>
))}
</>
);
}
export default App;
In this corrected code, the useEffect
hook ensures that the data fetching operation only occurs once, when the component mounts, preventing the infinite rendering loop (due to the empty dependency array []
). The studentList
state is updated after the asynchronous data request is complete, and the component renders correctly.
Let’s discuss useEffect in some more detail along with difference phases of a functional component. Here are the different phases of a functional component and how useEffect
can be used to achieve various tasks during these phases:
Mounting Phase:
Initialisation: useEffect
with an empty dependency array runs once after the initial render. It simulates componentDidMount
in class components.
useEffect(() => {
// Code to run after the initial render
}, []);
Updating Phase:
Dependencies Change: If any dependencies specified in the dependency array change, the useEffect
callback is re-executed. It is similar to componentDidUpdate
in class components.
const [value, setValue] = useState('');
useEffect(() => {
// Code to run when value changes
}, [value]);
Without Dependencies: If you don’t provide a dependency array, the useEffect
callback runs after every render. It is similar to componentDidUpdate
without specific dependencies.
useEffect(() => {
// Code to run after every render
});
Unmounting Phase:
Cleanup: useEffect
can be used to return a cleanup function, simulating componentWillUnmount
in class components.
useEffect(() => {
// Code to run on component mount
return () => {
// Code to run on component unmount
};
}, []);
Note: useEffect
without dependencies can lead to unintentional side effects and reduced performance, so it's generally a good practice to specify dependencies when possible. Also, always clean up resources in the cleanup function to prevent memory leaks.
In summary, useEffect
provides better control over the component's lifecycle and ensures a more predictable behavior when dealing with asynchronous operations like data fetching.
Thanks for Reading!!!