The useEffect
hook is the combination of the componentDidMount
, componentDidUpdate
, and componentWillUnmount
class lifecycle methods. This hook is the ideal place for setting up listeners, fetching data from APIs, and removing listeners before the component is removed from the DOM.
The useEffect
function is like saying, “Hi React, please do this thing after you render. ALWAYS.”
Let’s look at an example of useEffect
in comparison with class lifecycle methods. Normally, in a class component, we write this kind of code:
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
role: 'web developer',
name: 'Nathan',
};
}
componentDidMount() {
console.log(
`didMount: Hello I'm ${this.state.name} and I'm a ${this.state.role}`
);
}
componentDidUpdate() {
console.log(
`didUpdate: Hello I'm ${this.state.name} and I'm a ${this.state.role}`
);
}
render() {
return (
<div>
<p>{`Hello I'm ${this.state.name} and I'm a ${this.state.role}`}</p>
<button
onClick={() =>
this.setState({ name: 'Gary', role: 'data scientist' })
}
>
Change me
</button>
</div>
);
}
}
Since componentDidMount
is only run once when the component is inserted into the DOM tree structure, subsequent renders won’t trigger the method anymore. In order to do run something on each render, you need to use componentDidUpdate
method.
Since useEffect
runs on every render, using useEffect
hook is like having both componentDidMount
and componentDidUpdate
in one single method. It accepts two arguments:
useEffect
will be skipped if none of the variables are updated.Rewriting the above class into a functional component would look like this:
const Example = props => {
const [name, setName] = useState('Nathan');
const [role, setRole] = useState('web developer');
useEffect(() => {
console.log(`Hello I'm ${name} and I'm a ${role}`);
});
return (
<div>
<p>{`Hello I'm ${name} and I'm a ${role}`}</p>
<button
onClick={() => {
setName('Gary');
setRole('data scientist')
}}>
Change me
</button>
</div>
)
}
The functional component we just wrote will run the function inside useEffect
on each render. Now, this isn’t optimal because the state won’t be updated after the first click. This is where useEffect
's second argument comes into play.
useEffect(() => {
console.log(`Hello I'm ${name} and I'm a ${role}`);
}, [name, role] );
By adding the array above, React will skip running the console.log
method when there is no change made to the state variables.
componentWillUnmount
and skipping componentDidUpdate
partYou might have some code that needs to run when the component is removed from the DOM tree. In the useEffect
hook, you can specify a componentWillUnmount
method by returning a function from the first argument. Here is an example:
useEffect(() => {
console.log(`Hello I'm ${name} and I'm a ${role}`);
return () => { console.log("componentWillUnmount"); }
}, [name, role] );
Since componentWillUnmount
is used for cleaning whatever is left behind by your component, it’s really hard to give a practical example, but one example might be when using a third-party library like C3.js from Ashley Wilson.
We can rewrite his code from this:
componentDidMount () {
this._initGraph();
}
componentWillUnmount () {
this.graph = this.graph.destroy();
}
to this:
useEffect(() => {
this._initGraph();
return () => { this.graph = this.graph.destroy(); }
}, [] );
Do you wonder why we pass an empty array in the second argument? That’s because we want the hook to run this._initGraph()
only once on componentDidMount
. You can pass an empty array []
to tell React that this component should never re-render.
useEffect
is the function component way to create lifecycle methods, and in the spirit of React hooks, it does make React code cleaner while using fewer lines of code.
Free Resources