How to use useEffect() in React?
Managing Side Effects in React Functional Components with useEffect()
Introduction
React developers rejoice! The useEffect()
hook is here to save the day! It's the Batman to our Robin, the peanut butter to our jelly, the...well, you get the point. useEffect()
is a hook that allows you to perform side effects in your functional components, and it's so easy even your grandma could use it (no offense to your grandma).
What's a "side effect" in React?
Think of it like this: if your component is the party, then side effects are the aftermath. They're the things that happen after the component has rendered and they can be things like fetching data from an API, updating the DOM, setting timers, or making a sandwich. Okay, maybe not that last one, but you get the idea.
Check out my previous post to know more about side effects and when to use useEffect(). Make sure you read them before proceeding further.
How to...
There are 3 easy steps that you need to follow to use useEffect()
:
Import useEffect() and declare an Effect. By default, your Effect will run after every render. Effect is nothing but a function that you need to perform after React renders your component. (To dive deeper into Effects, check out this article)
Specify the dependencies for the Effect. Sometimes, you don't need your Effects to re-run after every render right? That's why you specify the dependencies for the Effect. It tells React to re-run the Effect only when the state of the dependencies changes and not on every render.
Add cleanup if needed. Some Effects need to specify how to stop, undo, or clean up whatever they were doing. For example, “connect” needs “disconnect”, “subscribe” needs “unsubscribe”, and “fetch” needs either “cancel” or “ignore”. You will learn how to do this by returning a cleanup function.
Let’s look at each of these steps in detail.
Step 1: Declaration
Import
useEffect()
hook from reactimport {useEffect} from 'react';
Call it at the top level of the component where you need to use it
const MyComponent = () => { useEffect(() => { // Write your Effect // Code here will run at every render by default! }); return <h1> Hola </h1>; };
Since there is no dependency array specified, the code inside
useEffect()
will run every time after the component renders. In other words,useEffect()
will delay the code inside it until the component is rendered completely.
Sometimes, a piece of code must run only after the component is rendered completely.
An uncommon scenario
Problem: Suppose you want to give a default value to an input field whenever a user visits that page. Of course, you can do it within the
<input/>
tag itself. But let's say you wanna do it without using it for some reason.Let's go ahead and try it:
We are using ref to update the value of the input tag. But as you can see, it is showing an error! Can you figure out why?
That's because we are trying to access the current property of the ref even before the component is rendered. This means that the
<input/>
tag hasn't even appeared on the screen yet!Here's how you do it: Just wrap the
ref.current.value="Hey";
withinuseEffect()
!import "./styles.css"; import { useEffect, useRef } from "react"; export default function App() { const ref = useRef(); useEffect(() => { ref.current.value = "hey"; }); return ( <div className="App"> <h1>Hello CodeSandbox</h1> <input type="text" name="msg" ref={ref} /> </div> ); }
(Go ahead and copy this code to the above sandbox and verify it!)
Step 2: The Dependency Array for useEffect()
So, imagine you're a chef who loves to bake cookies. You have a special recipe for chocolate chip cookies that requires a few key ingredients - flour, sugar, eggs, and of course, chocolate chips.
In React terms, these ingredients are like the dependencies of your useEffect
hook. Just like you need all these ingredients to bake your cookies, useEffect
needs all its dependencies to work properly.
Now, let's say you want to use useEffect
to track the number of cookies you've baked. You might write something like this:
useEffect(() => {
console.log(`You've baked ${numCookies} cookies!`);
}, [numCookies]);
In this example, numCookies
is the dependency that useEffect needs to keep track of. Whenever numCookies
changes, useEffect will run the code inside its function (in this case, logging the number of cookies you've baked).
But let's say you forget to include numCookies
in the dependency array:
useEffect(() => {
console.log(`You've baked ${numCookies} cookies!`);
});
Now, useEffect
won't be able to track changes to numCookies
. It's like trying to bake cookies without flour - it just won't work!
So, remember to always include your dependencies in the dependency array for useEffect
, or else your code might end up like a batch of cookies without chocolate chips - still good, but not quite as delicious as they could be!
A Common Scenario
Sure! The dependency array in the useEffect
hook is used to specify which variables or values should trigger a re-run of the effect function when they change. Here's a common scenario where the dependency array is used:
Let's say you have a component that fetches data from an API and renders it on the screen. You want to update the data whenever the user clicks a button, so you use the useEffect
hook to fetch the data:
function MyComponent() {
const [data, setData] = useState([]);
useEffect(() => {
fetchData().then((newData) => {
setData(newData);
});
}, []); // <-- dependency array is empty
return (
<div>
{data.map((item) => (
<div key={item.id}>{item.name}</div>
))}
<button onClick={() => {
// update data here
}}>Update Data</button>
</div>
);
}
In this example, the useEffect
hook is run only once, when the component mounts, because the dependency array is empty. But you want to fetch the data again whenever the user clicks the "Update Data" button.
To achieve this, you can add data
as a dependency in the dependency array:
function MyComponent() {
const [data, setData] = useState([]);
useEffect(() => {
fetchData().then((newData) => {
setData(newData);
});
}, [data]); // <-- add data to dependency array
return (
<div>
{data.map((item) => (
<div key={item.id}>{item.name}</div>
))}
<button onClick={() => {
fetchData().then((newData) => {
setData(newData);
});
}}>Update Data</button>
</div>
);
}
Now, the useEffect
hook will be re-run whenever data
changes, which happens when the user updates the data by clicking the "Update Data" button.
In summary, a common scenario for the use of the dependency array is when you want to re-run an effect function when certain variables or values change. By adding these variables to the dependency array, you can ensure that the effect runs at the appropriate times, and keeps your component in sync with its environment.
In the scenario I provided, the function specified in the onClick
handler is almost identical to the one specified in the useEffect
hook. In this case, it may seem redundant to use both.
However, in practice, the useEffect
hook is often used for more complex logic, such as updating state based on multiple dependencies, subscribing to event listeners, or cleaning up resources when the component unmounts. In those cases, it may not be appropriate to duplicate that logic in an onClick
handler.
Additionally, using useEffect
can help keep your code organized and maintainable. By separating the effect logic from the event handling logic, you can more easily reason about how your component works, and make changes to one part without affecting the other.
So while it may not always be necessary to use both useEffect
and an onClick
handler, it's good practice to consider the benefits of each and use them as appropriate for your specific use case.
Step 3: Cleanup
Imagine you're a very forgetful person who often forgets to turn off the lights when leaving a room. Your partner complains about the high electricity bills, so you decide to use a smart light switch that can be controlled with your phone. You install the switch and write a React component to control it:
function LightSwitch() {
const [isOn, setIsOn] = useState(false);
useEffect(() => {
if (isOn) {
console.log("Turning the lights on!");
} else {
console.log("Turning the lights off!");
}
return () => {
console.log("Cleaning up after myself!");
setIsOn(false);
};
}, [isOn]);
return (
<div>
<button onClick={() => setIsOn(!isOn)}>
{isOn ? "Turn off the lights" : "Turn on the lights"}
</button>
</div>
);
}
In this example, the useEffect
hook is used to log a message whenever the lights are turned on or off. Additionally, a cleanup function is defined to set isOn
to false
when the component unmounts. This ensures that the lights are always turned off when the component is no longer being used.
Now, imagine you're testing this component and you forget to turn off the lights before closing the browser window. Without the cleanup function, the lights would stay on even though the component is no longer being used! But thanks to the cleanup function, the lights will be turned off automatically when the component unmounts.
Of course, in real life, you might not need a cleanup function for a light switch - but the concept still applies to other scenarios, such as unsubscribing from event listeners or closing database connections. By using a cleanup function in useEffect
, you can ensure that your component behaves correctly and doesn't leave any "messes" behind.
Conclusion
Hope this article has given you an understanding of using the useEffect()
hook in React. Be sure to like the article and subscribe to the blog before you go. If you have any thoughts or queries, just make use of the comment section! 😊