The useEffect
hook is the combination of componentDidMount
, componentDidUpdate
and componentWillUnmount
class lifecycle methods. This hook is the ideal place to setup listener, fetching data from API and removing listeners before component is removed from the DOM.
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 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 run only once when the component is inserted into the DOM tree structure, subsequent render won’t trigger the method anymore. In order to do run something on each render, you need to use componentDidUpdate
method.
Using useEffect
hook is like having both componentDidMount
and componentDidUpdate
in one single method, since useEffect
runs on every render. It accepts two arguments:
- (mandatory) A function to run on every render
- (optional) An array of state variables to watch for changes.
useEffect
will be skipped if none of the variables are updated.
Rewriting the above class into function 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>
)
}
Note: If you’re confused with the useState
hook above, please refer to my useState
introduction
The function component we just write will run the function inside of useEffect
function on each render. Now this isn’t optimal because the state won’t be updated after the first click. This is where useEffect
second argument come 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 to the state variables.
The componentWillUnmount
and skipping componentDidUpdate
part
You might have some code that need to run when the component will be removed from the DOM tree. In 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 left behind by your component, it’s really hard to give some practical example. But one example might be when using 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();
}
into 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 didMount
. You can pass an empty array []
to tell React that this component never re-render.
Conclusion
useEffect
is the function component way to create lifecycle methods, and in the spirit of React hooks, it does make React code cleaner and use fewer lines of code.
More about React: