In this post, we will see how to replicate the same behavior we have in componentDidUpdate()
using react hooks
.
What is componentDidUpdate()?
componentDidUpdate()
is a class component lifecycle method that is invoked immediately after an update occurs. This method is very useful when we want to perform some action on a state change.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
increment = () => {
this.setState((prevState) => ({
count: prevState.count + 1,
}));
};
doSomething = () => {
console.log(`You clicked ${count} times`);
};
componentDidUpdate(prevProps) {
if (this.props.count !== prevProps.count) {
// invoced only when count updates
this.doSomething();
}
}
render() {
return (
<button type="button" onClick={this.increment}>Click</button>
);
}
}
export default App;
As you can see the doSomething()
method is invoked only when the count
prop is updated.
How can we perform an action on state change in function components
?
We cannot use lifecycle methods
in function components
. One way to perform some action in function components
after a prop state change is by using the useEffect
hook.
What does the useEffect do?
The useEffect
hook lets us perform side effects in function components
.
By default, useEffect
runs after every render.
useEffect(() => {
// runs after every render
console.log('render!');
});
To avoid running effects in every render we can pass an array of dependencies as a second argument.
useEffect(() => {
console.log(`You clicked ${count} times`);
}, [count]); // only re-run the effect if count changes
Does this mean we can replicate the same behavior we have in the componentDidUpdate()
by just passing the array of dependencies as a second argument?
There is a problem with this approach. Unlike componentDidUpdate()
the useEffect
runs not only when the dependency count
updates, but it runs as well on the initial render when the component mounts.
There is a way to fix that. We can combine the useEffect
with the useRef
hook.
import { useState, useEffect, useRef } from 'react';
const App = () => {
const [count, setCount] = useState(0);
const didMount = useRef(false);
useEffect(() => {
if (didMount.current) {
console.log(`You clicked ${count} times`);
} else {
didMount.current = true;
}
}, [count]);
const increment = () => {
setCount(count + 1);
};
return (
<button type="button" onClick={increment}>
Click
</button>
);
};
export default App;
As you can see, first we check if the component did mount, and then we run the effect when the dependency count
updates.
To make it better, we can create a custom hook and reuse it anytime we want.
import { useState, useEffect, useRef } from 'react';
const useDidUpdate = (callback, dependencies) => {
const didMount = useRef(false);
useEffect(() => {
if (didMount.current) {
callback();
} else {
didMount.current = true;
}
}, [callback, dependencies]);
};
const App = () => {
const [count, setCount] = useState(0);
useDidUpdate(() => {
console.log(`You clicked ${count} times`);
}, [count]);
const increment = () => {
setCount(count + 1);
};
return (
<button type="button" onClick={increment}>
Click
</button>
);
};
export default App;