React and componentDidReceiveProps

April 2, 2016
react programming

Thesis: Is there a case to be made for React setting props and state earlier in the component lifecycle?

Do you have this boilerplate in your React class?

var Thing = React.createClass({
  ...
  componentDidMount() {
    this.loadUserPosts(this.props);
  },
  
  // Detect and react to changes in the userID prop
  componentWillReceiveProps(nextProps) {
    if (this.props.userID !== nextProps.userID) {
      this.loadUserPosts(nextProps);
    }
  },
  
  // Loads all posts for the current user
  loadUserPosts: function(props) {
    ... load all posts for props.userID
  }
  ...
});

If so, you are not the only one.

READMORE

Look closely at why loadUserPosts() takes a props parameter.

This boilerplate is common because usually you want to fetch your data when your component is mounted (componentDidMount) and whenever a specific prop changes (componentWillReceiveProps). In this pattern, the key items in those methods are this.props and nextProps respectively and because of that difference, we need to create a method which takes a parameter creating the ugly boilerplate.

Let’s look quickly at the component lifecycle methods as defined by React:

  function componentWillReceiveProps(nextProps) {...}
  function shouldComponentUpdate(nextProps, nextState) {...}
  function componentWillUpdate(nextProps, nextState) {...}
  function render() {...}
  function componentDidUpdate(prevProps, prevState) {...}

The component lifecycle methods in React mostly operate on the principle that your component wants to anticipate a pending change to state and props. It’s not until the render() method that this.props and this.state are updated to the new values.

If you have a lot of different methods that react to changes in props, you might have written a lot of these methods that accept a props parameter. Github React Issue #3279 requests a componentDidReceiveProps precisely to remove that annoying boilerplate.

Enter componentDidReceiveProps()

As a thought experiment, let’s imagine we have a method from React called componentDidReceiveProps(prevProps) in which props had already assigned to this:

var Thing = React.createClass({
  ...
  componentDidMount() {
-    this.loadUserPosts(this.props);
+    this.loadUserPosts();
  },
  
  // Detect and react to changes in the userID prop
-  componentWillReceiveProps(nextProps) {
-    if (this.props.userID !== nextProps.userID) {
-      this.loadUserPosts(nextProps);
+  componentDidReceiveProps(prevProps) {
+    if (this.props.userID !== prevProps.userID) {
+      this.loadUserPosts();
    }
  },
  
  // Loads all posts for the current user
-  loadUserPosts: function(props) {
-    ... load all posts for props.userID
+  loadUserPosts: function() {
+    ... load all posts for this.props.userID
  }
  ...
});

This all looks good except that we have deeper problem: shouldComponentUpdate will be called after componentDidReceiveProps and shouldComponentUpdate keeps the paradigm of anticipating change in the nextProps, nextState parameters. In shouldComponentUpdate, this.state/props is still the “old” state and props.

Would we want React to assign the new state and props to this for componentDidReceiveProps just to reassign the old values for shouldComponentUpdate? And not only that, but there is still componentWillUpdate to call before render.

We’d need to change more.

What would life be like if React’s lifecycle methods had already set this.state/props?

To continue with the experiment, let’s see what it looks like if React assigns values to this.state/props for every lifecycle method and passes in the old values.

var Thing = React.createClass({
  componentDidReceiveProps: function(prevProps) {
    // ...
  },
  shouldComponentUpdate: function(prevProps, prevState) {
    // ...
  },
  componentWillUpdate: function(prevProps, prevState) {
     // ...
  },
  render: function() {
    // ...
  }
});

With these in place, it’s still possible to compare the old vs, new props, but this.props/state is now at the state it is in render.

This is all fun to think about, but what exactly would the impact be of changing it? I don’t know, but I looked through all of the React code in the office and this is what I found:

                                         | Total

———————————————|————- Total number of components | 297 With componentWillReceiveProps | 40 With componentWillReceiveProps boilerplate | 19

  • For shouldComponentUpdate, no component did more than compare the current props with the next props.
  • Only one component implemented componentWillUpdate with more than a simple comparison and any methods called from it, had to take a parameter for nextState/Props, suffering the same boilerplate as componentWillReceiveProps.

Using a different paradigm for fun

If you are curious to experiment with a different implementation, here is a JSFiddle that contains a v2 function in which you can wrap your React class definition.

Final notes

This hack only applies to the “conventional” way of creating React classes. An ES6 solution is left as an exercise for the reader (don’t you just love that writer’s escape hatch!).

Additionally, there is no guarantee this is safe to do. Certainly it’s worth investigating the React source code to see how dangerous such a “hack” is.

comments powered by Disqus