React is one of the popular JavaScript libraries for building user interfaces. When building these interfaces, you may need to perform side effects, such as fetching data from an API, subscribing to events, or manipulating the DOM.
That’s where the powerful useEffect
Hook comes into play. It allows you to seamlessly handle these side effects declaratively and efficiently, ensuring your UI stays responsive and up-to-date.
Whether you’re new to React or an experienced developer, understanding and mastering useEffect
is essential for creating robust and dynamic applications. In this article, you’ll learn how the useEffect
Hook works and how to use it in your React project.
What Is Side-Effect in React?
When working with React components, there are times when we need to interact with entities or perform actions outside of React’s scope. These external interactions are known as side effects.
In React, most components are pure functions, meaning they receive inputs (props) and produce predictable output (JSX), as seen in the example below:
export default function App() {
return <User userName="JaneDoe" />
}
function User(props) {
return <h1>{props.userName}</h1>; // John Doe
}
However, side effects are not predictable because they involve interactions outside of React’s usual scope.
Consider an example where you want to dynamically change the title of the browser tab to display the user’s userName
. While it might be tempting to do this directly within the component, it’s not the recommended approach because this is considered a side effect:
const User = ({ userName }) => {
document.title = `Hello ${userName}`; // ❌Never do this in the component body — It is a side effect.
return <h1>{userName}</h1>;
}
Performing side effects directly within the component body can interfere with the rendering process of our React component.
To avoid interference, you should separate side effects so they only render or function after our component has rendered, ensuring a clear separation between the rendering process and any necessary external interactions. This separation is done with the useEffect
Hook.
Understanding the Basics of useEffect
The useEffect
Hook is designed to mimic lifecycle methods like componentDidMount
, componentDidUpdate
, and componentWillUnmount
found in class components.
To use useEffect
, you will need to import it from “react” and then call it within a function component (at the top level of the component). It takes two arguments: a callback function and an optional dependency array.
useEffect(callbackFn, [dependencies]);
This can be better written as:
useEffect(() => {
// code to run when the effect is triggered
}, [dependencies]);
- The callback function contains the code to run when the component renders or the dependency value changes. This is where you perform side effect(s).
- The dependency array specifies the values that should be monitored for changes. The callback function will be executed when any value in this array changes.
For example, you can now correct the previous example to perform the side effect properly within a useEffect
Hook:
import { useEffect } from 'react';
const User = ({ userName }) => {
useEffect(() => {
document.title = `Hello ${userName}`;
}, [userName]);
return <h1>{userName}</h1>;
}
In the example above, the useEffect
Hook will be called after the component has rendered and whenever the dependency — userName
’s value — changes.
Working With Dependencies In useEffect
Dependencies play a crucial role in controlling the execution of useEffect
. It is the second argument of the useEffect
Hook.
useEffect(() => {
// code to run when the effect is triggered
}, [dependencies]);
Using an empty dependency array []
ensures the effect runs only once, simulating componentDidMount
. Specifying dependencies correctly allows the effect to update when specific values change, similar to componentDidUpdate
.
Note: You should take care when dealing with complex dependencies. Unnecessary updates can be avoided by carefully selecting which values to include in the dependency array.
Omitting the dependency array altogether will cause the effect to run every time the component renders, which can lead to performance issues.
useEffect(() => {
// code to run when the effect is triggered
});
In React, understanding how rendering works is a huge plus because you will be able to know the importance of the dependency array.
How Does Rendering Work in React?
In React, rendering generates the user interface (UI) based on a component’s current state and props. There are different scenarios where rendering occurs. The initial render happens when a component is first rendered or mounted.
Aside from this, a change in the state
or props
of a component triggers a re-render to ensure that the UI reflects the updated values. React applications are built with a tree-like structure of components, forming a hierarchy. React starts from the root component during rendering and recursively renders its child components.
This means all components would be rendered if a change occurs in the root component. It’s important to note that calling side effects(which are most times expensive functions) on every render can be costly. To optimize performance, you can use the dependency array in the useEffect
Hook to specify when it should be triggered, limiting unnecessary re-renders.
Advanced Usage of useEffect: Cleaning Up Side Effects
The useEffect
Hook allows us to perform side effects and provides a mechanism to clean up those side effects. This ensures that any resources or subscriptions created during the side effect are properly released and prevents memory leaks.
Let’s explore how you can clean up side effects using the useEffect
Hook:
useEffect(() => {
// Perform some side effect
// Cleanup side effect
return () => {
// Cleanup tasks
};
}, []);
In the code snippet above, the cleanup function is defined as a return value within the useEffect
Hook. This function is invoked when the component is about to unmount or before a subsequent re-render occurs. It allows you to clean up any resources or subscriptions established during the side effect.
Here are some examples of advanced usage of the useEffect
Hook for cleaning up side effects:
1. Clearing Intervals
useEffect(() => {
const interval = setInterval(() => {
// Perform some repeated action
}, 1000);
return () => {
clearInterval(interval); // Clean up the interval
};
}, []);
In this example, we set up an interval that performs an action every second. The cleanup function clears the interval to prevent it from running after the component is unmounted.
2. Cleaning Event Listeners
useEffect(() => {
const handleClick = () => {
// Handle the click event
};
window.addEventListener('click', handleClick);
return () => {
window.removeEventListener('click', handleClick); // Clean up the event listener
};
}, []);
Here, we create an event listener for the click event on the window object. The cleanup function removes the event listener to avoid memory leaks and ensure proper cleanup.
Remember, the cleanup function is optional, but it is highly recommended to clean up any resources or subscriptions to maintain a healthy and efficient application.
Using the useEffect Hook
The useEffect
Hook enables you to perform tasks that involve interacting with external entities or APIs, such as web APIs like localStorage or external data sources.
Let’s explore the usage of the useEffect
Hook with various scenarios:
1. Working with Web APIs (localStorage)
useEffect(() => {
// Storing data in localStorage
localStorage.setItem('key', 'value');
// Retrieving data from localStorage
const data = localStorage.getItem('key');
// Cleanup: Clearing localStorage when component unmount
return () => {
localStorage.removeItem('key');
};
}, []);
In this example, the useEffect
Hook is used to store and retrieve data from the browser’s localStorage. The cleanup function ensures that the localStorage is cleared when the component is unmounted (this may not be a good use case always as you may want to keep the localStorage data until the browser is refreshed).
2. Fetching Data From an External API
useEffect(() => {
// Fetching data from an external API
fetch('https://api.example.com/data')
.then((response) => response.json())
.then((data) => {
// Do something with the data
});
}, []);
Here, the useEffect
Hook is used to fetch data from an external API. The fetched data can then be processed and used within the component. It is not compulsory to add a cleanup function always.
Other Popular Side Effects
The useEffect
Hook can be used for various other side effects, such as:
A. Subscribing to Events:
useEffect(() => {
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);
B. Modifying the Document Title:
useEffect(() => {
document.title = 'New Title';
return () => {
document.title = 'Previous Title';
};
}, []);
C. Managing Timers:
useEffect(() => {
const timer = setInterval(() => {
// Do something repeatedly
}, 1000);
return () => {
clearInterval(timer);
};
}, []);
Common useEffect Errors and How To Avoid Them
While working with the useEffect
Hook in React, it’s possible to encounter errors that can lead to unexpected behavior or performance issues.
Understanding these errors and knowing how to avoid them can help ensure smooth and error-free usage of useEffect
.
Let’s explore some common useEffect
errors and their solutions:
1. Missing Dependency Array
One common mistake is forgetting to include a dependency array as the second argument of the useEffect
Hook.
ESLint, will always flag this as a warning because it can result in unintended behaviours, such as excessive re-rendering or stale data.
useEffect(() => {
// Side effect code
}); // Missing dependency array
Solution: Always provide a dependency array to useEffect
, even if it’s empty. Include all the variables or values that the effect depends on. This helps React determine when the effect should run or be skipped.
useEffect(() => {
// Side effect code
}, []); // Empty dependency array or with appropriate dependencies
2. Incorrect Dependency Array
Providing an incorrect dependency array can lead to issues as well. If the dependency array is not accurately defined, the effect might not run when the expected dependencies change.
const count = 5;
const counter = 0;
useEffect(() => {
// Side effect code that depends on 'count'
let answer = count + 15;
}, [count]); // Incorrect dependency array
Solution: Make sure to include all the necessary dependencies in the dependency array. If the effect depends on multiple variables, include all of them to trigger the effect when any of the dependencies change.
const count = 5;
useEffect(() => {
// Side effect code that depends on 'count'
let answer = count + 15;
}, [count]); // Correct dependency array
3. Infinite Loops
Creating an infinite loop can happen when the effect modifies a state or prop that is also dependent on the effect itself. This leads to the effect being triggered repeatedly, causing excessive re-rendering and potentially freezing the application.
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1); // Modifying the dependency 'count' inside the effect
}, [count]); // Dependency array includes 'count'
Solution: Ensure that the effect does not directly modify a dependency that is included in its dependency array. Instead, create separate variables or use other state management techniques to handle necessary changes.
const [count, setCount] = useState(0);
useEffect(() => {
setCount((prevCount) => prevCount + 1); // Modifying the 'count' using a callback
}, []); // You can safely remove the 'count' dependency
4. Forgetting Cleanup
Neglecting to clean up side effects can lead to memory leaks or unnecessary resource consumption. Not cleaning up event listeners, intervals, or subscriptions can result in unexpected behavior, especially when the component unmounts.
useEffect(() => {
const timer = setInterval(() => {
// Perform some action repeatedly
}, 1000);
// Missing cleanup
return () => {
clearInterval(timer); // Cleanup missing in the return statement
};
}, []);
Solution: Always provide a cleanup function in the return statement of the useEffect
Hook.
useEffect(() => {
const timer = setInterval(() => {
// Perform some action repeatedly
}, 1000);
return () => {
clearInterval(timer); // Cleanup included in the return statement
};
}, []);
By being aware of these common useEffect
errors and following the recommended solutions, you can avoid potential pitfalls and ensure the correct and efficient usage of the useEffect
Hook in your React applications.
Summary
React’s useEffect
Hook is a powerful tool for managing side effects in function components. Now that you have a deeper understanding of useEffect
, it’s time to apply your knowledge and bring your React applications to life.
You can also make your React application run live by deploying to Kinsta’s Application hosting for free!
Now it’s your turn. What is your thought on the useEffect
Hook? Please feel free to share it with us in the comments section below.