shouldComponentUpdate and children


(Dead Mg) #1

I’m interested in applying shouldComponentUpdate to our application for performance improvements. Unfortunately, I’ve come across what I think is a slight leakage in the interface, which is that the parent needs to consider whether or not each child needs to re-render when deciding whether or not to re-render. But conceptually, these are two completely different things- the parent may not need to update when the child does. For example, the parent may simply render the child inside a fixed wrapper.

I think that this couples the parent to the children too tightly, and wastes performance as the parent cannot indicate that it doesn’t need to update.

I suggest the introduction of another method, shouldUpdateChildren. If you return false from shouldComponentUpdate, the method is called. If the method returns true, then the children of that component should be considered as needing updates/update checks of their own. This way a component can let any children worry about their own updates and keep the components maximally separated.


(Sophie Alpert) #2

Just so we’re on the same page, can you give a code example of what you mean?


(andhapp) #3

Isn’t the parent-child relationship intentionally strong for a purpose? For instance, could you really use a td without a table.

However, I am sure you are dealing with a non-trivial issue. Please share the code snippet or a bit more information on your parent and child components.


(Denar50) #4

Hello. I was going to open a discussion on this same matter, but I am just going to comment it here since I have a similar (if not the same) problem. Here is the plunker.
Basically I have a component that wraps content. In that simple example I have a component called MyWrapperComponent and it just wraps its children.

var MyWrapperComponent = React.createClass({
	shouldComponentUpdate: function(nextProps) {
    return this.props.foo !== nextProps.foo;
  },
	render: function() {
  	return (
    	<div>
        value of foo:	{this.props.foo}
        <div>
        	{this.props.children}
        </div>
      </div>
    )
  }
})

I have implemented the shouldComponentUpdate method of MyWrapperComponent to assert whether the only property, foo, it receives has changed or not. Since MyWrapperComponent is just a wrapper, it is completely agnostic of what its children are and so it doesn’t know if its children changed.

Now this is the AppComponent where I use MyWrapperComponent. As you can see I am wrapping a diff that renders “bar” in MyWrapperComponent.

var AppComponent = React.createClass({
	updateFoo: function() {
  	this.setState({
			foo: this.state.foo + 1       
    })
  },
  updateBar: function() {
  	this.setState({
    	bar: this.state.bar + 1
    })
  },
	getInitialState: function() {
  	return {
    	foo: 1,
      bar: 2
    };
  },
	render: function() {
  	return (
  	 <div>
    <button onClick={this.updateFoo}>Update foo</button>
    <button onClick={this.updateBar}>Update bar</button>
    	<MyWrapperComponent foo={this.state.foo}>
      	<div>
        	value of bar: {this.state.bar}
        </div>
      </MyWrapperComponent>
      </div>
    );
  }
})

When I update foo, the wrapper component renders as expected, but if I update bar, the wrapper component won’t get rerendered because of its shouldComponentUpdate method.

Obviously this example is quite simple, but in the real life problem the wrapper is quite complex and react perf tools is telling me it is wasting a lot of time and that’s why I decided to implement shouldComponentUpdate and found this problem.

So my question is: is there any way to deal with this problem? Any documentation that I should read? is there any good or advised practice here?

Thanks again!


(Borisyankov) #5

Component updating works exactly as expected.
You can also compare

this.props.foo !== nextProps.foo

On the other hand, you can leave the shouldComponent update in the children.

shouldComponentUpdate is not necessary that often. If when the component is rendered again, it is equal to the previous render, no DOM manipulation is done. And if you keep the render method fast, you can skip the shouldComponentUpdate altogether.


(Denar50) #6

this.props.children != nextProps.children
will always return true no matter what, which makes adding that verification to the shouldComponentUpdate pointless

For this particular case it is impossible to implement shouldComponentUpdate because the children are not custom components themselves.

sometimes you do, and in the real case of this example I do need to implement it.

no DOM manipulation is done, but react wastes trying to figure that out, which is what I want to prevent with shouldComponentUpdate


(Adam Terlson) #7

Very interesting question, thank you for asking it.

The problem is that you’re misconstruing the nature of this.props.children. You’ve constructed a child component with a componentShouldUpdate working as expected while in reality asking how to have the child update when the parent does not. This is not possible, because this is explicitly what shouldComponentUpdate is meant to prevent.

@borisyankov’s suggestion also will not work, but not because of your reason given, but because a component’s passed in children do not update if the parent they are passed into does not update. (proof) So, making your child text a component does not work.

You’re conceptualizing this.props.children as if it were somehow owned or evaluated in the outer context, but this is not the case. Think instead of this.props.children as the same as all other props. You can either think of it as a reference to a function which returns a result (in your case “value of bar…”) or as an object which contains a render method. Either is invoked during the parent component’s render method (parent here refers to the component the child is passed into as a prop).

Basically, though the JSX syntax (and specifically your scenario) makes it look as if children are rendered in the context of the parent, this is not the case.

(plunker)

// pseudo components
const BarComponent = ({ bar }) => ({
    render() {
      return 'value of bar: ' + bar;
    }
});
const FooComponent =({ foo, children }) => ({
    render() {
        return `
            foo: ${foo}
            children: ${children.render()}
        `;
     }
});

const MyComponent = ({ foo, bar }) => ({
    render() {
        return FooComponent({ foo, children: BarComponent({ bar }) }).render();
    }
});

Note how children is a reference to an object with a render method that is invoked when its own render is executed. Given that this is the case, is it reasonable to expect it’s in any way possible for the user to see updated values of BarComponent if FooComponent never re-executes?

Nope!


(Bastoche) #8

As a consequence, one should no override shouldComponentUpdate for a component that renders children passed as props ?


(Alex Guerra) #9

Essentially yes, wrapping components should not use shouldComponentUpdate. There are caveats, but if the children ever change across renders of the shared parent it is pretty much a no go.

Hypothetically you could deep diff children, but that’s not a perfect solution either and would take some work (though it only needs to be written once). Maybe there’s already a library to do this.


(Mickeyjc) #10

Since you are passing child component as children props inside the wrapping component, shouldn’t you also compare the children props in your shouldComponentUpdate in the wrapper too?

shouldComponentUpdate: function(nextProps) {
return (!_.isEqual(this.props.children, nextProps.children) || … other condition);
}


(GPLTaylor) #11

This is where ImmutableJS may come to the rescue. Its simple reference check is all that’s needed to test any deep object for changes.


(James Gillmore) #12

I’m having the same issue. I happen to be using React Native Web. I’ve created a parent wrapper component that serves the purpose of triggering animations. Under the hood it uses the Animated API which came from React Native. Essentially I have components like this:

<AnimatedView translateX{this.props.x} duration={300}>
   <Child foo={bar} />
</AnimatedView>

If bar and this.props.x change at the same time, the animation will trigger, but <Child> won’t update. For what I’m doing this is very important because AnimatedView uses shouldComponentUpdate to trigger the animation via the Animated API while preventing re-renderings to achieve the goal of a smooth animation. If the re-rendering happens at the same as the animation, the animation is glitchy.

Perhaps this is a React Native Web issue, but I doubt it. I actually started with React Native before using plain React on the web–what has everyone else experienced in regular React? Do animations become glitchy if re-renderings happen at the same time?

I have a very nice general animation component I’m quite happy with. I’ll share it with you all. It essentially takes the imperative Animated API and makes it declarative:

So the goal for me is to solve the issue of allowing for updates to both the parent and its children in the same tick. The only thing I can think of is requiring client code to set a timer to update the child component in a following tick. There must be something that can be done to allow for this pattern and avoid such hacks.


(Sergey Gavrilov) #13

wow this behavior surprises.

so shouldComponentUpdate returning false prevents the component update. but does not prevent child components update, except ones that were passed dynamically through props (ie. this.props.children).

  1. should not this be noted in docs for shouldComponentUpdate?
    it is not immediately obvious that child components are only normal static child components and not dynamic child components from this.props.children.

  2. why update static children, but not dynamic ones?
    I understand reasoning behind not updating dynamic children that @adamterlson described.
    But static children still get updated.
    Is there any implementation difference between static/dynamic children?
    Or update of dynamic children intentionally prevented?

@sophiebits


(Nick Roberts) #14

I think there is a simple and effective solution to the problems posed here (or many of them).

Essentially, what you do is to put the parent into a child, and invent a new dummy parent.

As an example, suppose I have a component Car with four children, each a Wheel. I want to be able to update some or all of the wheels without necessarily updating the car. Supposing all the complicated, time-consuming stuff for rendering the car can be put into a new component named Chassis. Now we can have an extra child of Car, a Chassis, and remove all the complicated chassis rendering stuff from the car. And now we can update any of the wheels without necessarily updating the chassis.

Does this make sense?


(Jovica Aleksic) #15

The children passed in via props are out of scope, or at least out of control, from a component’s perspective. A component is entirely incapable of “updating” these children even if it wanted to - they have been already updated outside of it, before they were passed in via props.
This simply means that the very same updating-logic still applied, but it was “moved up in the hierarchy”, to the parent component. That’s the one that decided whether or not to render our component in question, and what children to render and pass down to it via props. In fact, the entire idea of passing children is to make a component agnostic of these children: the component knows nothing about them besides them being “children” that came “from outside”.


(Sergey Gavrilov) #16

Agree dynamic children are out of control from receiving component, but they are under control of react which knows previously rendered tree, and can walk down that tree to initiate update on each child component - the same way as does for static children.

In fact, I assume from react's perspective there is no difference if children are defined statically or received from props. Resulting rendered tree what’s matters. Next time when I decide to skip updating parent component - its render method will not be called, but children still get updated, (why?) because react has result of previous render tree and walks down it initiating updates.

Which means ether I’m wrong about this assumption, or react intentionally prevent update on dynamic children.


(Sergey Gavrilov) #17

Wrong. React does not attempt to update children if parent shouldComponentUpdate returns false.
I totally misunderstood the concept. Sadly nobody pointed me to that nonsense above.

So shouldComponentUpdate works the same for any children (static or dynamic), and everything looks good about it.