Author avatar

Chris Dobson

Reacting to Prop Changes in a React Component

Chris Dobson

  • Mar 28, 2019
  • 7 Min read
  • 10,796 Views
  • Mar 28, 2019
  • 7 Min read
  • 10,796 Views
Web Development
React

Introduction

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.

Starting Component

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:

1
2
3
function Label({text}) {
 return <span className="label-text">{text}</span>;
}
javascript

Functional Component

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Label({text}) {
  const [backgroundColor, setBackgroundColor] = React.useState("inherit");
  const updateTimer = React.useRef(null);

  function setUpdate() {
    setBackgroundColor("#9b34ee");
	updateTimer.current = setTimeout(() => {
	  setBackgroundColor("inherit");
	  updateTimer.current = null;
	}, 1000);
  }

  return (
	<span className="label-text" style={{ backgroundColor: backgroundColour }}>
	  {text}
	</span>
  );
}
javascript

The Effect Hook

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:

1
React.useEffect(() => { console.log("component updated"); });
javascript

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:

1
2
3
4
5
React.useEffect(() => {
  if(!updateTimer.current) {
    setUpdate();
  }
}, [text]);
javascript

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:

1
2
3
4
5
6
7
React.useEffect(()=> {
  return () => {
    if (updateTimer.current) {
      clearTimeout(updateTimer.current);
	}
  };
}, []);
javascript

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.

Class Component

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 arender` method. This code looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Label extends React.Component {
  state = { backgroundColour: "inherit" };
	
  componentWillUnmount() {
    if (this.updateTimer) {
      clearTimeout(this.updateTimer);
	}
  }

  updateAndNotify = () => {
    if (this.updateTimer) return;
    this.setState({ backgroundColour: "#9b34ee" });
    this.updateTimer = setTimeout(() => {
      this.setState({ backgroundColour: "inherit" });
	  this.updateTimer = null;
	}, 1000);
  }

  render() {
    return (
	  <span className="label-text" style={{ backgroundColor: this.state.backgroundColour }}>
	    {this.props.text}
	  </span>
	);
  }
}
javascript

`componentDidUpdate`

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:

1
2
3
4
5
componentDidUpdate(prevProps) {
  if (prevProps.text !== this.props.text) {
    this.updateAndNotify();
  }
}
javascript

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.

`getSnapshotBeforeUpdate`

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:

1
2
3
4
5
6
7
8
9
getSnapshotBeforeUpdate(prevProps) {
  return { notifyRequired: prevProps.text !== this.props.text };
}

componentDidUpdate(prevProps, prevState, snapshot) {
  if (snapshot.notifyRequired) {
    this.updateAndNotify();
  }
}
javascript

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.

Conclusion

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.

24