When building a component using React there is often a requirement to create a side effect when one of the component props changes. This could be a call to an API to fetch some data, manipulating the DOM, updating some component state, or any number of things. This guide will show ways of creating these side effects in both functional components using React hooks and class components.
The guide will start with a very simple label component that will have a prop called text
and display it inside a span
, then extend this component to highlight the text when the prop is changed by the parent component. The implementation of the text highlighting will set the component state to a background color, set a timeout of one second, and set the state back to the original background color.
The code for the starting component looks like this:
1function Label({text}) {
2 return <span className="label-text">{text}</span>;
3}
In version 16.8, React hooks were introduced. Hooks allow a component to be built as a function without the need for classes.
This component will need a state variable to track the background color and a ref to store the current timer instance. Although refs are primarily used to access the DOM the useRef
hook can also be used to store a mutable variable that will not trigger an update of the component when changed. It will also need a function to set the state to a color, wait for a second, and then set it back to the default value. The markup returned by the component will be the same as the original label with the addition of setting the style. The code to do all of this is here:
1function Label({text}) {
2 const [backgroundColor, setBackgroundColor] = React.useState("inherit");
3 const updateTimer = React.useRef(null);
4
5 function setUpdate() {
6 setBackgroundColor("#9b34ee");
7 updateTimer.current = setTimeout(() => {
8 setBackgroundColor("inherit");
9 updateTimer.current = null;
10 }, 1000);
11 }
12
13 return (
14 <span className="label-text" style={{ backgroundColor: backgroundColour }}>
15 {text}
16 </span>
17 );
18}
Now that the component has everything ready to create the highlight side effect, all that is needed is to call the setUpdate
function when the text
prop has been changed. The way to do this in the hooks API is by using the effect hook.
The effect hook can either run after every render, only on mount and unmount, or when specified values have been changed. It takes two parameters - the first is the function to execute and the second is an optional array of variables that, when changed, will trigger execution of the function.
If the hook is called with no second parameter then the function will be called every time the component is updated like this:
1React.useEffect(() => { console.log("component updated"); });
This component will require two effect hooks. The first is to be called only when the text prop has been updated and will check if there is no highlight in progress and, if not, will call the setUpdate
function defined in the previous section:
1React.useEffect(() => {
2 if(!updateTimer.current) {
3 setUpdate();
4 }
5}, [text]);
The second effect hook this component requires is one to clean up the timer reference when the component unmounts. This can be achieved by passing an empty array as the second parameter and returning a function that will then be called when the component is being umounted. The code for this looks like:
1React.useEffect(()=> {
2 return () => {
3 if (updateTimer.current) {
4 clearTimeout(updateTimer.current);
5 }
6 };
7}, []);
Add these two calls to useEffect
into the Label function and the background color will now highlight for a second whenever the text prop changes.
This guide will explore two ways of achieving the highlighting using a class component. Both will require a class extending Component
, a method called updateAndNotify
that will set the background color state and then set it back to the initial value after a second, a componentWillUnmount’ method to clear the timer up, and a
render` method. This code looks like this:
1class Label extends React.Component {
2 state = { backgroundColour: "inherit" };
3
4 componentWillUnmount() {
5 if (this.updateTimer) {
6 clearTimeout(this.updateTimer);
7 }
8 }
9
10 updateAndNotify = () => {
11 if (this.updateTimer) return;
12 this.setState({ backgroundColour: "#9b34ee" });
13 this.updateTimer = setTimeout(() => {
14 this.setState({ backgroundColour: "inherit" });
15 this.updateTimer = null;
16 }, 1000);
17 }
18
19 render() {
20 return (
21 <span className="label-text" style={{ backgroundColor: this.state.backgroundColour }}>
22 {this.props.text}
23 </span>
24 );
25 }
26}
Whenever a class component is updated, the componentDidUpdate
method is called. The first parameter for this method is the props before the update; so, testing whether a prop has changed can be done here. For this component the method will look like this:
1componentDidUpdate(prevProps) {
2 if (prevProps.text !== this.props.text) {
3 this.updateAndNotify();
4 }
5}
The above is simply testing if prevProps.text
is different from this.props.text
and, if it is, then calling the method to update the state.
When using eslint, you may see an error or a warning about setting state inside the componentDidUpdate
method. Setting state inside this method without any guard can cause an infinite loop, in this instance we are only setting state if the text prop has been changed; so it is fine.
The getSnapshotBeforeUpdate
method can also be used to test for changes to props. This method is called before the componentDidUpdate
method in the component life cycle and returns a value that is passed into the componentDidUpdate
method as the third parameter. To implement the highlighting component, the getSnapshotBeforeUpdate
method should return an object containing a field set to true or false depending on whether the text prop has been changed and then the componentDidUpdate
method should check that field and, if true, call the method to update the state. Like this:
1getSnapshotBeforeUpdate(prevProps) {
2 return { notifyRequired: prevProps.text !== this.props.text };
3}
4
5componentDidUpdate(prevProps, prevState, snapshot) {
6 if (snapshot.notifyRequired) {
7 this.updateAndNotify();
8 }
9}
This use case is not how getSnapshotBeforeUpdate
would typically be used; as it would make more sense to do everything in the componentDidUpdate
method but this has been included to illustrate that this method could be used to identify prop changes.
It is relatively simple to add side-effects into both functional and class components using React. The code for the components in this guide can be found here.
Explore these React courses from Pluralsight to continue learning: