Whenever we need to make use of side effects in our application, useEffect
hook is the way to go in React functional components.
In this article, we will learn about the useEffect
hook in React, its uses, and implementation with the help of an example.
Let’s get started.
What exactly is an “effect”?
The word effect refers to a functional programming term called a “side effect”. But to really understand what a side effect is, we first have to grasp the concept of a pure function.
Most React components are pure functions, meaning they receive an input and produce a predictable output of JSX.
The input to a JavaScript function is arguments. What is the input to a React component, however? Props!
App.jsx
function App() { return ( <> <div> <User name="John Doe" /> </div> </> ); } export default App;
User.jsx
import React from 'react'; const User = (props) => { return ( <div> <h1>{props.name}</h1> </div> ); }; export default User;
Here we have a User component that has the prop name
declared on it. Within User, the prop value is displayed in a header element.
This is pure because, given the same input, it will always return the same output. If we pass User a name prop with value “John Doe”, our output will always be John Doe.
You might be saying, “Who cares? Why do we even have a name for this?”
Pure functions have the great benefit of being predictable, reliable, and easy to test. This is as compared to when we need to perform a side effect in our component.
What are side effects in React?
Side effects are not predictable because they are actions which are performed with the “outside world.”
We perform a side effect when we need to reach outside of our React components to do something. Performing a side effect, however, will not give us a predictable result.
Think about if we were to request data (like blog posts) from a server that has failed and instead of our post data, gives us a 500
status code response.
📣 Also Read: The SEO’s Guide to HTTP Status Codes
Virtually all applications rely on side effects to work in one way or another, aside from the simplest applications.
Common side effects include:
- Making a request to an API for data from a backend server
- To interact with browser APIs (that is, to use
document
orwindow
directly) - Using unpredictable timing functions like
setTimeout
orsetInterval
This is why useEffect exists: to provide a way to handle performing these side effects in what are otherwise pure React components.
For example, if we wanted to change the title meta tag to display the user’s name in their browser tab, we could do it within the component itself, but we shouldn’t.
User.jsx
import React from 'react'; const User = ({ name }) => { document.title = name; // This is a side effect. Don't do this in the component body! return ( <div> <h1>{name}</h1> </div> ); }; export default User;
App.jsx
import User from './User'; function App() { return ( <> <div> <User name="John Doe" /> </div> </> ); } export default App;
Output:
If we perform a side effect directly in our component body, it gets in the way of our React component’s rendering.
Component rendering and side-effect logic are independent. So it would be a mistake to perform side-effects directly in the body of the component.
Side effects should be separated from the rendering process. If we need to perform a side effect, it should strictly be done after our component renders.
This is what useEffect
gives us.
In short, useEffect
is a tool that lets us interact with the outside world but not affect the rendering or performance of the component that it’s in.
useEffect() Syntax
The basic syntax of useEffect
is as follows:
// 1. import useEffect import { useEffect } from 'react'; function MyComponent() { // 2. call it above the returned JSX // 3. pass two arguments to it: a function and an array useEffect(() => {}, []); // return ... }
The correct way to perform the side effect in our User
component is as follows:
- We import
useEffect
from “react” - We call it above the returned JSX in our component
- We pass it two arguments: a function and an array
User.jsx
import { useEffect } from 'react'; const User = ({ name }) => { useEffect(() => { document.title = name; }, [name]); return ( <div> <h1>{name}</h1> </div> ); };
App.jsx
import User from './User'; function App() { return ( <> <div> <User name="John Doe" /> </div> </> ); } export default App;
The function passed to useEffect
is a callback function. This will be called after the component renders.
đź“Ł Also Read: Understand Callbacks In JavaScript
In this function, we can perform our side effects or multiple side effects if we want.
The second argument is an array, called the dependencies array. This array should include all of the values that our side effect relies upon.
Kindly remember, dependencies is an optional array of dependencies. useEffect
hook executes callback only once the page loads.
In our example above, since we are changing the title based off of a value in the outer scope, name
, we need to include that within the dependencies array.
What this array will do is it will check and see if a value (in this case name
) has changed between renders. If so, it will execute our use effect function again.
This makes sense because if the name changes, we want to display that changed name and therefore run our side effect again.
Dependencies Argument (Ways of Controlling Side Effects in useEffect Hook)
Dependencies argument of useEffect
hook lets you control when the side effect runs. If dependencies are:
A) Not Provided: The side effect runs after every rendering
import { useEffect } from 'react'; function MyComponent() { useEffect(() => { // Runs after EVERY rendering }); }
B) An Empty Array []: The side effect runs once after the initial rendering
import { useEffect } from 'react'; function MyComponent() { useEffect(() => { // Runs ONCE after initial rendering }, []); }
C) Has props or state values [prop1, prop2, …, state1, state2]: The side effect runs once after initial rendering and then only when any dependency value changes
import { useEffect, useState } from 'react'; function MyComponent({ prop }) { const [state, setState] = useState(''); useEffect(() => { // Runs ONCE after initial rendering // and after every rendering ONLY IF `prop` or `state` changes }, [prop, state]); }
Let’s detail cases B) and C) since they are often used.
useEffect Hook & Component Lifecycle
The dependencies argument of the useEffect
hook lets you catch certain component lifecycle events: when the component has been mounted or a specific prop or state value has changed.
Component Lifecycle – componentDidMount
We can use an empty dependencies array to invoke a side effect once after component mounting.
Greet.jsx
import { useEffect } from 'react'; const Greet = ({ name }) => { const message = `Hello, ${name}!`; useEffect(() => { // Runs once, after mounting document.title = 'Greetings page'; }, []); return <div>{message}</div>; }; export default Greet;
useEffect(…, [])
was supplied with an empty array as the dependencies argument. When configured in such a way, the useEffect
hook executes the callback just once, after initial rendering (mounting).
App.jsx
import Greet from './Greet'; function App() { return ( <> {/* First render */} <Greet name="Sunil" /> //Side-effect RUNS {/* Second render, name prop changes */} <Greet name="Anil" /> // Side-effect DOES NOT RUN {/* Third render, name prop changes */} <Greet name="Gopal" /> // Side-effect DOES NOT RUN </> ); } export default App;
Even if the component re-renders with different name property, the side effect runs just once after initial rendering.
Output:
Component Lifecycle – componentDidUpdate
Each time the side effect uses props
or state
values, you must indicate these values as dependencies:
import { useEffect } from 'react'; function MyComponent({ prop }) { const [state, setState] = useState(); useEffect(() => { // Side-effect uses `prop` and `state` }, [prop, state]); return <div>....</div>; }
useEffect(callback, [prop, state])
invokes the callback once after mounting, and again after committing the changes to the DOM, if and only if any value in the dependencies array [prop, state]
has changed.
By using the dependencies argument of useEffect
hook we control when the side effect is called, independently from the rendering cycles of the component. Again, that’s the essence of useEffect
hook.
Let’s improve the Greet
component by using name
prop in the document title:
Greet.jsx
import { useEffect } from 'react'; const Greet = ({ name }) => { const message = `Hello, ${name}!`; useEffect(() => { document.title = `Greetings to ${name}`; }, [name]); return <div>{message}</div>; }; export default Greet;
App.jsx
import Greet from './Greet'; function App() { return ( <> {/* First render */} <Greet name="Sunil" /> //Side-effect RUNS {/* Second render, name prop changes */} <Greet name="Anil" /> // Side-effect RUNS {/* Third render, name prop doesn't change */} <Greet name="Anil" /> // Side-effect DOES NOT RUN {/* // Fourth render, name prop changes */} <Greet name="Gopal" /> // Side-effect RUNS </> ); } export default App;
Output:
What is the cleanup function in useEffect?
The final part of performing side effects properly in React is the effect cleanup function.
Sometimes our side effects need to be shut off. For example, close a socket, clear timers.
If the callback of useEffect(callback, deps)
returns a function, then useEffect
hook considers that function as an effect cleanup.
useEffect(function callback() => { // Side-effect... return function cleanup() { // Side-effect cleanup... }; }, dependencies);
Cleanup works the following way:
A) After initial rendering, useEffect
hook invokes the callback with the side effect. cleanup function is not invoked.
B) On later renderings, before invoking the next side effect callback, useEffect
hook invokes the cleanup function from the previous side effect execution (to clean up everything after the previous side effect), then invokes the current side effect.
C) Finally, after unmounting the component, useEffect
hook invokes the cleanup function from the latest side effect.
Let’s see an example of when the side effect cleanup is useful.
RepeatMessage.jsx
import { useEffect } from 'react'; const RepeatMessage = ({ message }) => { useEffect(() => { setInterval(() => { console.log(message); }, 2000); }, [message]); return <div>I'm logging to console "{message}"</div>; }; export default RepeatMessage;
App.jsx
import React, { useState } from 'react'; import RepeatMessage from './RepeatMessage'; function App() { const [message, setMessage] = useState('Hello, World!'); return ( <> <h3>Type the message to log to console</h3> <input type="text" value={message} onChange={(e) => setMessage(e.target.value)} /> <RepeatMessage message={message} /> </> ); } export default App;
Output:
When you type some messages. The console logs every 2 seconds the messages typed into the input. However, you need to log only the latest message.
That’s the case to clean up the side effect: cancel the previous timer when starting a new one. Let’s return a cleanup function that stops the previous timer before starting a new one:
RepeatMessage.jsx
import { useEffect } from 'react'; const RepeatMessage = ({ message }) => { useEffect(() => { const id = setInterval(() => { console.log(message); }, 2000); return () => { clearInterval(id); }; }, [message]); return <div>I'm logging to console "{message}"</div>; }; export default RepeatMessage;
App.jsx
import React, { useState } from 'react'; import RepeatMessage from './RepeatMessage'; function App() { const [message, setMessage] = useState('Hello, World!'); return ( <> <h3>Type the message to log to console</h3> <input type="text" value={message} onChange={(e) => setMessage(e.target.value)} /> <RepeatMessage message={message} /> </> ); } export default App;
Now when you type some messages. You will see that every 2 seconds only the latest message logs to the console. Which means that all of the previous timers were cleaned up.
Result:
useEffect Hook in Practice
Example 1 – Random Meme Generator
We can use useEffect
hook to fetch data from a server and display it without problems.
In the code snippet below, we will fetch meme images from the meme API and display a new image whenever a button clicks.
Meme.jsx
import React, { useState, useEffect } from 'react'; const Meme = () => { const [memeData, setMemeData] = useState({ randomImage: 'https://i.imgflip.com/26jxvz.jpg', }); const [allMemeImages, setAllMemeImages] = useState([]); useEffect(() => { fetch('https://api.imgflip.com/get_memes') .then((res) => res.json()) .then((data) => setAllMemeImages(data.data.memes)); }, []); const handleClick = () => { const randomNum = Math.floor(Math.random() * allMemeImages.length); const url = allMemeImages[randomNum].url; setMemeData((prevState) => { return { ...prevState, randomImage: url, }; }); }; return ( <div style={{ textAlign: 'center', marginTop: '30px' }}> <button onClick={handleClick}>Get a new meme image</button> <div style={{ textAlign: 'center', marginTop: '50px' }}> <img src={memeData.randomImage} alt="meme-images" /> </div> </div> ); }; export default Meme;
App.jsx
import './App.css'; import Meme from './Meme'; function App() { return ( <> <Meme/> </> ); } export default App;
Result:
The code above does the following:
- Import
useEffect
anduseState
from React - Declares the initial state for
memeData
to display an image on the first render of the component withsetMemeData
as the setter function - Declares the initial state for
allMemeImages
to store all the images from the meme API endpoint useEffect
hook is called, and the fetch function is used to make a call to the meme API and store all the data using thesetAllMemeImages
. An empty dependency array indicates that the meme API is called once with theuseEffect
hook- Declares
handleClick
function to get a new image when the button is clicked. This function randomly generates a URL from the list of all the image URL gotten from the meme API and stored in theallMemeImages
state. In this function,setMemeData
is used to re-render the component on every click, which makes the image change to another image on the screen
Example 2 – Counter
Initially, the document title reads “You clicked 0 times”. when you click on the button, the count value increments, and the document title is updated.
HookCounterOne.jsx
import { useState, useEffect } from 'react'; const HookCounterOne = () => { const [count, setCount] = useState(0); useEffect(() => { document.title = `You clicked ${count} times`; }, [count]); return ( <div> <button onClick={() => setCount((prevCount) => prevCount + 1)}> Click {count} times </button> </div> ); }; export default HookCounterOne;
App.jsx
import HookCounterOne from './HookCounterOne'; function App() { return ( <> <HookCounterOne /> </> ); } export default App;
Result:
Conclusion
And that’s the gist of useEffect
hook! i.e.
useEffect hook can be used to handle lifecycle events, such as
componentDidMount
,componentDidUpdate
, andcomponentWillUnmount
, in a functional component.
Thanks for reading this article. We hope it was helpful for React beginners.
Please feel free to ask questions in the comments below. Ultimately, practicing and building projects with this hook will help anyone to pick it up faster.
Resource
- A Complete Guide to useEffect
- 6 use cases of the useEffect React hook
- React useEffect Hook Examples
- A Simple Explanation of React.useEffect()
Add comment