Intro to useEffect()
Just like other React hooks, useEffect is defined as a function. It gives you the ability to conditionally execute code for a component re-render. If the code results in updating state each time, it might be a good idea to wrap that code within a useEffect hook. This ensures it runs one time or based on a condition. More on this during code samples.
Initializing useEffect()
useEffect doesn’t return any data. However, you pass two arguments to it. The first argument is setup as a callback function. This includes the code you want to execute. The second argument is optional and is called the dependency array. Having an empty dependency array tells useEffect to only execute the code defined in the first argument during initial rendering of the component. To use the useEffect() hook, it must be imported from the React library where you intend to use it.
Why useEffect Matters
This is a component called WinterTime. It consists of a button on the page called Days of Winter. When clicking the button, it updates state and the value of state is displayed within the h1 element. In this example, state is a number that starts at zero and works as a counter when updated. This part of the code works fine and no issues. The problem with this code is line 12. When the component mounts, line 11 executes which calls the function winterTime which starts on line 3. That function also updates state which causes a component refresh. The refresh of the component causes all code to execute again in the component executing line 11 again. This keeps going and results in an infinite loop.
useEffect() to save the day
Assuming you only want code to run one time, you can wrap it in a useEffect hook. Yes, useEffect is a function just like other react hooks. It accepts two arguments; the first argument is a callback function that executes upon return and a second argument is a dependency array. Using an empty one ensures the function only runs once. To fix the above code and prevent looping, I would setup the following:
On line 1, import useEffect hook. This code prevents the infinite loop as I wrapped winterTime() call inside useEffect (Lines 12-13). On line 12, I setup a callback function as first argument which executes winterTime. The second argument is an empty dependency array. Because it’s empty, the winterTime() call on line 13 will only execute on initial render of the component. If I click the button multiple times, line 13 will not execute. Typical use case for useEffect()
useEffect() in the real world
A more common scenario when using useEffect hook is making api calls. Fetch API is a classic example and can be used to make asynchronous calls across a network to gather data. It uses a promise object to eventually return a value. The target is an API that returns JSON or XML. The following is an example of using useEffect hook with fetch.
The above code uses fetch to retrieve results from the pokemon api. It then writes out all the pokemon names as h4 elements.
Lines 8-17 – fetchData is an anonymous function setup to handle async/await to handle the asynchronous call from fetch (line 10). Assuming data is returned, it updates pokemon state (line 6).
Lines 19-21 – Because fetch updates state I only want to run it on initial render, this is possible by implementing the useEffect hook. I add the fetchData() as the callback function and pass an empty dependency array to ensure it only runs on initial render.
Line 25-33 – Because were working with json data, we can call map function to iterate and pass each item to a callback function which returns HTML including the name for each pokemon.
useEffect with dependencies
What if you only need code to execute when a value is updated? For example, perhaps you have a list of menu buttons that display menu items. When you click on a button, you want to show related details below the button. It would persist the menu details to the page and would only update if another menu button is clicked. Behind the scenes, doing the button click would fetch items from an API which returns details for the clicked menu item. Usually in this case, you would be working off multiple states. Here is a code example:
import { useState, useEffect } from "react";
function App() {
const [names, setNames] = useState([]);
const [menutime, setMenuTime] = useState(null);
const [menuitems, setMenuItems] = useState(null);
useEffect(() => {
fetch("/menu.json")
.then((response) => response.json())
.then((data) => setNames(data));
}, []);
useEffect(() => {
if (menutime !== null) {
fetch(`/${menutime}.json`)
.then((response) => response.json())
.then((data) => setMenuItems(data));
}
}, [menutime]);
return (
<>
<div>
<h2>Select a button to see a menu</h2>
{names &&
names.map((menu) => (
<button key={menu.id} onClick={() => setMenuTime(menu.name)}>
{menu.name}
</button>
))}
<div>
{menuitems &&
menuitems.map((entree) => (
<div key={entree.id}>
<div>Entree: {entree.item} </div>
<div>Price: {entree.price}</div>
</div>
))}
</div>
</div>
</>
);
}
export default App;
The first state contains menu item names which are displayed as buttons on the page. It’s populated from the initial useEffect which fetches results from a local json file. Because it has no dependencies, it only executes on initial mount. The second state stores the results of the button clicked by the user. When a user clicks a button, it sets the second state, menutime. That invokes a component refresh and the second useEffect runs which has a dependency on menutime. It changed and is not null so it fetches the results and stores them in the third state, menuitems. Finally, we ensure menuitems is not null and perform a map operation to drop in each entree item and price. It looks like:
Clicking Dinner button
While this works fine, it’s not the best approach because the second useEffect will still execute. The initial page load still mounts the component and state would be considered new even if it’s set to null. This is why I have an if check to ensure menutime is not null. A better approach is when button is clicked invoke a callback function which calls a function that contains your fetch for menu items. Then you wouldn’t need the second useEffect hook.
Code Samples from this hook and others available on my repo here: RussMaxwell/ReactHelp: This repo contains help info and details (github.com)
Thanks,
Russ Maxwell