useEffect is - as the name suggests - a hook to perform arbitrary side effects during a life of a component. So we see that this model differs from traditional lifecycle callbacks of a class components. It seems to be a bit stricter and more opinionated. useEffect hook is a mechanism for making side effects in functional components. Side effects should not be caused directly in components body or render function, but should always be wrapped in a callback passed to useEffect.
import {
useEffect
} from 'react';
const Example = () => {
return <div />;
};
import { useEffect } from 'react';
const Example = () => {
useEffect(() => {
console.log('render');
});
return <div />;
};
const Wrapper = () => {
// everything here stays the same as before
return (
<div>
<button onClick={updateCount}>{count}</button>
{count < 5 && <Example />}
</div>
};
const Example = () => {
useEffect(() => {
console.log('render');
return () => {
console.log('unmount');
};
});
return <div />;
};
With useEffect, you invoke side effects from within functional components, which is an important concept to understand in the React Hooks era. Working with the side effects invoked by the useEffect Hook may seem cumbersome at first, but you’ll eventually learn everything makes a lot of sense. The goal of this comprehensive article is to gather information about the underlying concepts of useEffect and, in addition, to provide learnings from my own experience with the useEffect Hook. For example, now that I have dealt with useEffect for quite some time, I have realized that it is key to fully understand the component flow of functional components. As such, this aspect is an important topic in this article.
The signature of the useEffect
Hook looks like this:
useEffect(
() => {
// execute side effect
},
// optional dependency array
[
// 0 or more entries
]
)
Because the second argument is optional, the following execution is perfectly fine:
useEffect(() => {
// execute side effect
})
Let’s take a look at an example. The user can change the document title with an input field.
import React, { useState, useRef, useEffect } from "react";
function EffectsDemoNoDependency() {
const [title, setTitle] = useState("default title");
const titleRef = useRef();
useEffect(() => {
console.log("useEffect");
document.title = title;
});
const handleClick = () => setTitle(titleRef.current.value);
console.log("render");
return (
<div>
<input ref={titleRef} />
<button onClick={handleClick}>change title</button>
</div>
);
}
Of course, it’s not a huge deal in this example, but you can imagine more problematic use cases that cause bugs or at least performance issues. Let’s take a look at the following code and try to read the initial title from local storage, if available, in an additional useEffect
block.
function EffectsDemoInfiniteLoop() {
const [title, setTitle] = useState("default title");
const titleRef = useRef();
useEffect(() => {
console.log("useEffect title");
document.title = title;
});
useEffect(() => {
console.log("useEffect local storage");
const persistedTitle = localStorage.getItem("title");
setTitle(persistedTitle || []);
});
console.log("render");
const handleClick = () => setTitle(titleRef.current.value);
return (
<div>
<input ref={titleRef} />
<button onClick={handleClick}>change title</button>
</div>
);
}
More often than not, this is what we want; we usually want to execute side effects after specific conditions, e.g., data has changed, a prop changed, or the user first sees our component. Another strategy to skip unnecessary effects is to prevent unnecessary re-renders in the first place with, e.g., React.memo
, as we’ll see later.
Back to our example where we want to skip unnecessary effects after an intended re-render, we just have to add an array with title
as a dependency. With that, the effect is only executed when the values between render cycles differ.
useEffect(() => {
console.log("useEffect");
document.title = title;
}, [title]);
Why is useEffect called inside a component? Placing useEffect inside the component lets us access the count state variable (or any props) right from the effect. We don’t need a special API to read it — it’s already in the function scope. Hooks embrace JavaScript closures and avoid introducing React-specific APIs where JavaScript already provides a solution. We’ve learned that useEffect lets us express different kinds of side effects after a component renders. Some effects might require cleanup so they return a function: In some cases, cleaning up or applying the effect after every render might create a performance problem. In class components, we can solve this by writing an extra comparison with prevProps or prevState inside componentDidUpdate:
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// Similar to componentDidMount and componentDidUpdate: useEffect(() => { // Update the document title using the browser API document.title = `You clicked ${count} times`; });
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
componentDidMount() { document.title = `You clicked ${this.state.count} times`; } componentDidUpdate() { document.title = `You clicked ${this.state.count} times`; }
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => { document.title = `You clicked ${count} times`; });
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
class FriendStatus extends React.Component {
constructor(props) {
super(props);
this.state = {
isOnline: null
};
this.handleStatusChange = this.handleStatusChange.bind(this);
}
componentDidMount() {
ChatAPI.subscribeToFriendStatus(this.props.friend.id, this.handleStatusChange);
}
componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(this.props.friend.id, this.handleStatusChange);
}
handleStatusChange(status) {
this.setState({
isOnline: status.isOnline
});
}
render() {
if (this.state.isOnline === null) {
return 'Loading...';
}
return this.state.isOnline ? 'Online' : 'Offline';
}
}
import React, {
useState,
useEffect
} from 'react';
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); // Specify how to clean up after this effect: return function cleanup() { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; });
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
A functional React component uses props and/or state to calculate the output. If the functional component makes calculations that don't target the output value, then these calculations are named side-effects. The document title update is the side-effect because it doesn't directly calculate the component output. That's why document title update is placed in a callback and supplied to useEffect(). useEffect(callback, dependencies) is the hook that manages the side-effects in functional components. callback argument is a function to put the side-effect logic. dependencies is a list of dependencies of your side-effect: being props or state values.
The component rendering and side-effect logic are independent. It would be a mistake to perform side-effects directly in the body of the component, which is primarily used to compute the output.
How often the component renders isn't something you can control — if React wants to render the component, you cannot stop it.
jsxfunction Greet({ name }) { const message = `Hello, ${name}!`; // Calculates output // Bad! document.title = `Greetings to ${name}`; // Side-effect! return <div>{message}</div>; // Calculates output}
How to decouple rendering from the side-effect? Welcome useEffect()
— the hook that runs side-effects independently of rendering.
jsximport { useEffect } from 'react';function Greet({ name }) { const message = `Hello, ${name}!`; // Calculates output useEffect(() => { // Good! document.title = `Greetings to ${name}`; // Side-effect! }, [name]); return <div>{message}</div>; // Calculates output}
useEffect()
hook accepts 2 arguments:
javascriptuseEffect(callback[, dependencies]);
dependencies
argument of useEffect(callback, dependencies)
lets you control when the side-effect runs. When dependencies are:
A) Not provided: the side-effect runs after every rendering.
jsximport {
useEffect
}
from 'react';
function MyComponent() {
useEffect(() => { // Runs after EVERY rendering }); }
B) An empty array []
: the side-effect runs once after the initial rendering.
jsximport {
useEffect
}
from 'react';
function MyComponent() {
useEffect(() => { // Runs ONCE after initial rendering }, []);}
Some examples of side effects are: fetching data, directly updating the DOM, and timers. The useEffect Hook allows you to perform side effects in your components. Here is an example of a useEffect Hook that is dependent on a variable. If the count variable updates, the effect will run again: What do you need to add to the second argument of a useEffect Hook to limit it to running only on the first render?
Use setTimeout()
to count 1 second after initial render:
import { useState, useEffect } from "react";
import ReactDOM from "react-dom/client";
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
setTimeout(() => {
setCount((count) => count + 1);
}, 1000);
});
return <h1>I've rendered {count} times!</h1>;
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Timer />);
useEffect(() => {
//Runs on every render
});
useEffect(() => {
//Runs only on the first render
}, []);
Only run the effect on the initial render:
import { useState, useEffect } from "react";
import ReactDOM from "react-dom/client";
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
setTimeout(() => {
setCount((count) => count + 1);
}, 1000);
}, []); // <- add empty brackets here
return <h1>I've rendered {count} times!</h1>;
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Timer />);
import { useState, useEffect } from "react";
import ReactDOM from "react-dom/client";
function Counter() {
const [count, setCount] = useState(0);
const [calculation, setCalculation] = useState(0);
useEffect(() => {
setCalculation(() => count * 2);
}, [count]); // <- add the count variable here
return (
<>
<p>Count: {count}</p>
<button onClick={() => setCount((c) => c + 1)}>+</button>
<p>Calculation: {calculation}</p>
</>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Counter />);
useEffect takes two arguments. The first argument passed to useEffect is a function called effect and the second argument (optional) is an array of dependencies. Below is an example. The second argument of useEffect is an array of dependencies. If you want to control when the effect runs after the component has been mounted, pass an array of dependencies as the second argument. Dependencies are values defined outside useEffect but used inside useEffect, such as: The effect runs when the component is mounted, and whether or not it runs on subsequent updates is determined by an array of dependencies passed as the second argument to react useEffect.
Does all this sound new and fascinating to you? You can learn more about this from mobile app development course, which has in-depth explanations of the core concepts of React. Let’s now continue on the useEffect, don’t forget to check out the link though.
Want more insights, check out the best React online course. The basic syntax is as follows:
// onMount
useEffect(() => {
console.log("I Only run once (When the component gets mounted)")
}, []);
The react useEffect Hook essentially replaces every single lifecycle function that you may run into.
useEffect(() => {
// Update the document title using the browser API document.title = `You clicked ${count} times`;
});
import { useEffect } from "react";
import { render } from "react-dom";
const App = (props) => {
useEffect(() => {
console.log("Effect ran");
}); //No second Argument
return <h1> KnowledgeHut By Upgrad! </h1>;
};
const root = document.getElementById("root");
render(<App />, root);
React will compare the current value of the constraint with the value from the previous render. If they are not equal, the effect is called. This argument is optional. If omitted, the effect will run after each render. You can pass an empty array if you only want the effect to run on the first render.
useEffect(() => {
console.log("Effect ran");
}, []) // the useEffect will now only be evoked once per render
Dependencies can be states or props. Note that values defined inside a component outside of useEffect must be passed as dependencies when used inside useEffect. This is illustrated below.
function App() {
const [count, setCount] = useState(1);
// count is defined in App, but outside of useEffect
useEffect(() => {
//since we are using count, we have to pass it as a dependency
console.log(count);
}, [count])
}
This is why useEffect exists: to provide a way to handle performing these side effects in what are otherwise pure React components. 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. Side effects are not predictable because they are actions which are performed with the "outside world."
It may be strange to think about React components as functions, but they are.
It helps to see that a regular React function component is declared like a JavaScript function:
function MyReactComponent() {}
The input to a JavaScript function is arguments. What is the input to a React component, however? Props!
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.
export default function App() {
return <User name="John Doe" />
}
function User(props) {
return <h1>{props.name}</h1>; // John Doe
}
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.
function User({ name }) {
document.title = name;
// This is a side effect. Don't do this in the component body!
return <h1>{name}</h1>;
}
The correct way to perform the side effect in our User
component is as follows:
import { useEffect } from 'react';
function User({ name }) {
useEffect(() => {
document.title = name;
}, [name]);
return <h1>{name}</h1>;
}
This can lead to problems when you're attempting to update state within your useEffect hook.
If you forget to provide your dependencies correctly and you are setting a piece of local state when the state is updated, the default behavior of React is to re-render the component. And therefore, since useEffect runs after every single render without the dependencies array, we will have an infinite loop.
function MyComponent() {
const [data, setData] = useState([])
useEffect(() => {
fetchData().then(myData => setData(myData))
// Error! useEffect runs after every render without the dependencies array, causing infinite loop
});
}