React Side Effects (useEffect)
Links: 115 React Index
Side Effects¶
- In general we will always have to make API calls to fetch some data in React.
- Now the problem is that if we fetch the data using the following logic out component will keep on rendering infinitely.
import React from "react";
export default function App() {
const [starWarsData, setStarWarsData] = React.useState({});
console.log("Component rendered");
fetch("https://swapi.dev/api/people/1")
.then((res) => res.json())
.then((data) => setStarWarsData(data));
return (
<div>
<pre>{JSON.stringify(starWarsData, null, 2)}</pre>
</div>
);
}
- The reason for infinite re-renders is that every time the component is initialised there will be an API call. Now because of the API call
setStarWarsData
will be called which will re-render the component which will make the API call again and this will keep on continuing. - Here API call is a side effect.
- Side effect are things which are outside the control of React.
- Some other examples are: local storage, web sockets, keeping two states in sync.
- We use the
useEffect
to manage these side effects.- It helps synchronise react state with outside systems.
useEffect
¶
- The function we pass to
useEffect
will run after the render of our component.
import React from "react";
export default function App() {
const [count, setCount] = React.useState(0)
console.log("Component rendered");
React.useEffect(() => {
console.log("Effect ran");
});
return (
<div>
<button onClick={() => setCount(prevCount => prevCount + 1)}>
Add
</button>
</div>
);
}
// Component rendered
// Effect ran
- Now if we click the Add button the component will be re-rendered again and we will see the same 2 console log outputs again.
- The second parameter to
useEffect
is known as the dependencies array.- Dependencies array contains values which when changed will cause the
useEffect
to run.
- Dependencies array contains values which when changed will cause the
Dependencies array determines when the useEffect
function will run instead of running after every single render.
- If we leave it as an empty array (
[]
) it tells React that there are no dependencies to watch out for. - This means it runs only once when the component first loads and that's it.
- If we wanted
useEffect
to run every time count changed we would have count in the dependencies array ([count]
).
- Having a constant value in the dependencies array is same as having an empty array.
useEffect
won't be run if the values of the dependencies in the dependency array remain the same.- If count is in the dependencies array then
useEffect
will only be run if the value of count changes.
- If count is in the dependencies array then
- Creating infinite renders using
useEffect
import React from "react";
export default function App() {
const [count, setCount] = React.useState(0)
console.log("Component rendered");
React.useEffect(() => {
console.log("Effect ran");
setCount(count++)
}, [count]);
return (
<div>
<button onClick={() => setCount(prevCount => prevCount + 1)}>
Add
</button>
</div>
);
}
-
This will cause infinite re-renders since we are depending on count and we are increasing count inside the
useEffect
. -
In this example we want our
useEffect
to run every time our count changes by clicking the next character button.
export default function App() {
const [starWarsData, setStarWarsData] = React.useState({});
const [count, setCount] = React.useState(1);
React.useEffect(
function () {
console.log("Effect ran");
fetch(`https://swapi.dev/api/people/${count}`)
.then((res) => res.json())
.then((data) => setStarWarsData(data));
},
[count] // important, this will run useEffect everytime count is changed i.e. the button is clicked
);
return (
<div>
<h2>The count is {count}</h2>
<button onClick={() => setCount((prevCount) => prevCount + 1)}>
Get Next Character
</button>
<pre>{JSON.stringify(starWarsData, null, 2)}</pre>
</div>
);
}
- Example to track the window width live:
- Since window width is something outside of React we have to use
useEffect
- Since window width is something outside of React we have to use
import React from "react";
export default function WindowTracker() {
const [windowWidth, setWindowWidth] = React.useState(window.innerWidth);
React.useEffect(() => {
window.addEventListener("resize", function () {
setWindowWidth(window.innerWidth);
});
}, [windowWidth]);
return <h1>Window width: {windowWidth}</h1>;
}
Why there is infinite re-rendering in the above example but there was infinite re-rendering when we modified count inside the useEffect
body.
- In the above example
windowWidth
only changes when the width of the window is changed. Although we have specifiedwindowWidth
as one of the dependencies in the dependency array, react will only re-render the component when the new value ofwindowWidth
is different from its original value. - In the example with count we were always incrementing the value of count inside the
useEffect
body. This meant the new value of count was always different from the original value thereby causing infinite re-renders. If inside theuseEffect
body we had set count to a fixed value then this would not have caused infinite re-renders.
We should be careful while modifying the state in the dependencies array inside the useEffect
body since it can cause infinite re-renders.
Clean up¶
- Clean up functions in
useEffect
helps us in preventing memory leaks. - An example would be creating a websocket connection with a chat API using
useEffect
.- Now after creating the subscription when we try to unmount the component it is always a good idea to sever the websocket connection.
import React from "react";
export default function WindowTracker() {
const [windowWidth, setWindowWidth] = React.useState(window.innerWidth);
React.useEffect(() => {
function watchWidth() {
console.log("Setting up...");
setWindowWidth(window.innerWidth);
}
window.addEventListener("resize", watchWidth);
// removing the event listener we have added
return function () {
console.log("Cleaning up...");
window.removeEventListener("resize", watchWidth);
};
}, []);
return <h1>Window width: {windowWidth}</h1>;
}
- An example of conditionally mounting and unmounting the component would be
{show && <WindowTracer />}
Using async functions¶
- If we want to use
async
way of making API calls inuseEffect
then we will have to create another function. - The reason being we CANNOT declare the call back function in
useEffect
to beasync
because when we do that a promise is returned but React is expecting a clean up function and not a promise.
useEffect
takes a function as its parameter. If that function returns something, it needs to be a cleanup function. Otherwise, it should return nothing. If we make it an async function, it automatically returns a promise instead of a function. Therefore, if you want to use async operations inside of useEffect
, you need to define the function separately inside of the callback function.
// WRONG
React.useEffect(async () => {
// API call using await
}
// CORRECT
React.useEffect(() => {
// separate async function
async function getMemes() {
const res = await fetch("https://api.imgflip.com/get_memes");
const data = await res.json();
setAllMemes(data.data.memes);
}
getMemes();
}, []);
Last updated: 2023-03-26