I have designed an application called Age Of Heroes. One of things that is integral to the function of my application is the useEffect hook.
There are many fun things for which we can use the useEffect hook. These will be discussed shortly. However, we need to know what it is and how it works.
The useEffect Hook lets one perform side effects in function components.
The purpose of a component function is to return JSX. If a function is called and causes change in our application outside of the function itself, it's considered to have caused a side effect. Side effects that are useful to us include fetch requests, starting and stopping timers, changing the DOM, and subscriptions.
By default, every time a component renders useEffect is run. However, one can modify when useEffect is called by having it return a cleanup function. Another way is to include a dependencies array. I will describe this in more detail using code examples like the one below.
import React, {useState, useEffect} from "react";
import DisplayHeroes from "./DisplayHeroes";
function App() {
const [heroesList, setHeroesList]=useState([])
useEffect(()=>{
fetch("http://localhost:3000/heroes")
.then(res=>res.json())
.then(heroes=>setHeroesList(heroes))
}, []) //Note empty dependencies array, only executes on 1st render
return (
<div>
<DisplayHeroes heroesList={heroesList} />
</div>
);
}
export default App;
For the above code, useEffect is used to fetch data from an API. Upon rendering of the App component useEffect is executed and a GET request is made and an array of superhero objects is obtained. In this case useEffect is only executed once. Why is that? Let me answer that question by talking about how the useEffect function works. Yes, that is right it is a function. As a matter of fact it takes two arguments.
The first argument is a callback function which contains the code that we want to execute. In our case we to execute a fetch request. The second argument is a dependencies array. As its name implies whether useEffect continues to run or not depends on the value within that array. If the variable inside the array changes then useEffect will execute, otherwise, it will not execute.
However, in the code above, the dependencies array is empty. This means that useEffect will only execute once when the component is first rendered.
At this juncture, I believe it is imperative to talk about the scenarios pertaining to dependencies arrays.
- If there is no dependencies array then the side effect runs every time our component renders (whenever state or props change)
- If there is an empty dependencies array then the side effect is run only the first time our component renders.
- If there is a dependencies array with elements in it then the side effect runs any time the variable(s) change.
useEffect(() => {}): No dependencies array
// executes every time its component renders
useEffect(() => {}, []): Empty dependencies array
// only executes the first time its component renders
useEffect(() => {}, [variable1, variable2, ...]): Dependencies array with elements in it
// executes anytime the variable(s) in the array change
In summary the App component above runs a useEffect function once when the component first renders to fetch an array of objects representing the profiles of superheroes. The value of this array is assigned to a state variable heroesList. It looks like this.
[{
id: 1,
name: "Superman (Clark Kent/Kal-El)",
image: "https://media0.giphy.com/media/3o6ZtqfJ38C7Yih5xm/200w.webp?cid=ecf05e47jxo7qo0x02xmxyetgke8rwi1ppjkqprbbjnh0vap&rid=200w.webp&ct=g",
powers: "flight, super strength, super speed, enhanced senses, enhanced durability",
weapons: "heat vision, freeze breath",
team: "Justice League",
quote: "There is a right and a wrong in this universe. And that distinction is not hard to make."
},
{
id: 2,
name: "Batman (Bruce Wayne)",
image: "https://media1.giphy.com/media/C7RCCFdaixA3u/giphy.gif?cid=ecf05e47bm9qqmunwsk3bzdnw9p4oyp2su5tght7tvjxoghn&rid=giphy.gif&ct=g",
powers: "peak human strength and agility, genius level intellect, immense wealth",
weapons: "utility belt, grappling gun, batarangs",
team: "Justice League",
quote: "I am the vengeance, I am the night, I am Batman!"
},
{
id: 3,
name: "Wonder Woman (Diana Prince)",
image: "https://media2.giphy.com/media/8cVE0r50yyRYlgqekf/200w.webp?cid=ecf05e47qvpp95k2vntaknjt1mx0kvvpp4d9zobynq6dgtwz&rid=200w.webp&ct=g",
powers: "super strength, super speed, superior combat skills",
weapons: "lasso of truth, bullet proof gauntlets, sword, shield",
team: "Justice League",
quote: "I Will Fight For Those Who Cannot Fight For Themselves."
}]
The variable heroesList is passed as props to the DisplayHeroes. DisplayHeroes subsequently renders a profile for each member of the DC universe trinity (Superman, Batman and Wonder Woman) which looks like this.
Now lets move on to another example of useEffect. The side effect of interest is a timer and a random number generator. Note that this case contains a dependency array with two variables. There is even a cleanup function which will be discussed soon.
import React from "react";
import { useState, useEffect } from "react";
function SlideShow (){
const [slides, setSlides] = useState([])
const [index, setIndex] = useState([1])
useEffect(()=>{
fetch("http://localhost:3000/slides")
.then(res=>res.json())
.then(slides=>setSlides(slides))
}, []) //Note empty dependencies array, only executes on 1st render
useEffect(()=>{
const intervalID=setInterval(()=>setIndex(Math.ceil(Math.random() * slides.length)), 5000)
return function(){clearInterval(intervalID)}
}, [index])
const displaySlides = slides.map(slide=>{
if (slide.id===index) {
return <img key={slide.id} src={slide.image} alt="action scene" className="slide" />
}
})
return (
<div className="slide-container">
<h2>Heroes In Action</h2>
{displaySlides}
</div>
)
}
export default SlideShow;
Note that the SlideShow component contains two useEffect functions. Yes, it can be used multiple times in a component. The first one utilizes a fetch function which is similar to the one from our sample code earlier. However, the second useEffect function uses a setInterval function and generates a index using a random number generator assigned to a state variable. This results in a slide show with a new image rendered every 5 seconds using the randomly generated index. Note that the dependencies array contains a value which is index. Every time the index changes useEffect is executed.
Also take note of the cleanup function. Of course, it would be nice to know what this is. A cleanup function is a function returned by useEffect. It serves a few purposes. For example, in our case the setInterval function is running as a side effect while the SlideShow component is mounted, the cleanup runs once the component is unmounted and stops the timer from running.
Essentially, it prevents unnecessary code from running in the background.
useEffect(()=>{
const intervalID=setInterval(()=>setIndex(Math.ceil(Math.random() * slides.length)), 5000) //generates index randomly every 5 sec
return function(){clearInterval(intervalID)} //cleanup function
}, [index])
//note dependencies array with value of index, runs everytime index changes
React performs the cleanup when the component unmounts. However, effects run for every render in this case and not just once. This is why React also cleans up effects from the previous render before running the effects next time. This helps avoid bugs and memory leaks. Just remember that sometimes cleanup functions are needed and sometimes they are unnecessary. Furthermore, cleanup is just a name assigned to the function, one can name it anything they want.
The net result is that our SlideShow component fetches an array of objects which looks similar to this.
[{
id: 1,
image: "https://media2.giphy.com/media/l2QZXqfxmtgXIDOPS/giphy.gif?cid=790b7611db0d15296d83085fabefdbc7d6fee22d1b00c214&rid=giphy.gif&ct=g"
},
{
id: 2,
image: "https://media2.giphy.com/media/AqfOVseMPDVja/giphy.gif?cid=790b761197bcd049128129edda81386a92a16c92a5b49a46&rid=giphy.gif&ct=g"
},
{
id: 3,
image: "https://media2.giphy.com/media/xT9DPtjydKl5FQJKIE/giphy.gif?cid=790b76115fdf37ec8952d0f6cf1488df801fabf9ba22d71b&rid=giphy.gif&ct=g"
},
{
id: 4,
image: "https://thumbs.gfycat.com/ArcticUnsteadyEyelashpitviper.webp"
}]
Our SlideShow component renders a slide show which shows our favorite superheroes at their finest. Let us take a look at a moment of this slide show.
Now that is a work of art. That is some superhero stuff right there. Sometime, when I write code, I feel a little heroic. But all heroics aside, let us recap what we learned.
- The useEffect hook is useful to execute desirable effects including fetching data from an API and timer based code.
- The useEffect function can take up to two arguments: a callback function and a dependencies array.
- By default useEffect is executed every time its component renders. However, this behavior can be modified with a dependencies array. An empty dependencies array allow it to execute only on the first render which is commonly used for fetching data. A dependencies array with a variable allows useEffect to execute every time there is a change in the variable.
- A cleanup function is optional. It is used to prevent unnecessary code from running in the background and avoid bugs in the code.
Thank you for your time and heroism.
Top comments (2)
Your
let intervalID
should be aconst
!Nice examples of the useEffect hook though. Be careful not to over use the useEffect hook for everything though. Having many (bad) side effects can have a serious impact on the performance of your app, because it can cause many unecessary rerenders, especially if you change state in your useEffect. If a child component starts to rerender because of that state change and that child component also has side effects that change state... And so on. My rule of thumb is to use the useMemo hook whenever possible and only use the useEffect hook for side effects that happen outside of the react render cycle, lik timeouts, intervals, fetching, etc. And even then be really careful that you put the proper dependencies in the dependency array and that those dependencies are all memoized, so they do not unintentionally trigger the side effect.
Thank you. I changed the let into a const. I will look into the useMemo hook since I am still learning.