Do you want to request a feature or report a bug?
Report a possible bug
What is the current behavior?
When Component A renders Component B with a children prop that is a react component / JSX fragment any render of component A will recreate said children prop
If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem via https://jsfiddle.net or similar (template: https://jsfiddle.net/reactjs/69z2wepo/).
Example Code:
import React from 'react';
class ComponentWithChildren extends React.PureComponent {
render() {
console.log("RENDER COMP")
return <span>Hello{this.props.children}</span>
}
}
class Children extends React.PureComponent {
render() {
return (
<div>
these are children
<span>nested</span>
</div>
)
}
}
class App extends React.PureComponent {
update = () => this.setState({ count: this.state.count + 1 })
state = {
count: 0
}
render() {
console.log("RENDER APP")
return (
<div>
<button onClick={this.update}>Update</button>
<ComponentWithChildren>
<Children />
</ComponentWithChildren>
</div>
)
}
}
export default App;
What is the expected behavior?
I would expect ComponentWithChildren not to re-render because none of its props actually changed
Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?
React 15.4.1
This is the expected behavior.
The reason ComponentWithChildren
re-renders every time is because <Children /> === <Children />
always evaluates to false. This may seem unintuitive, but it's because <Children />
is sugar for React.createElement(Children)
, which returns a new object every time it is called.
To fix, you can cache the <Children />
element to ensure that the value is preserved across renders:
class App extends React.PureComponent {
update = () => this.setState({ count: this.state.count + 1 })
state = {
count: 0
}
children = <Children />;
render() {
console.log("RENDER APP")
return (
<div>
<button onClick={this.update}>Update</button>
<ComponentWithChildren>
{this.children}
</ComponentWithChildren>
</div>
)
}
}
Hey, thanks for the answer.
what would be the appropriate solution if <Children />
needed to receive props?
If props are the same every time, you can hoist <Children prop={myProp} />
to a constant, just like in the example above.
If it receives different props every time then it’s not “the same” on updates. Therefore it makes sense for PureComponent
to not skip rendering since otherwise you won’t see any changes below. However you are free to write a Component
with a custom shouldComponentUpdate
implementation if you want to use even more granular heuristics to determine whether to update.
@gaearon thanks for chiming in, were having a lot of trouble with optimizing these cases. One approach we have taken is passing a renderChildren
function instead of passing children
at all. However children is a very readable syntax and I don't want to be too quick throwing out the baby with the bathwater.
The problematic case is mainly when you have children that depend on props which are dynamic but that doesn't mean they always change, I don't want the children to be recreated when the props don't change.
I know this sounds a bit like overkill but out goal currently is not to have a single unnecessary render (render being just entering the render function)
@davegri
You may be optimizing for the wrong thing. Running render()
itself is not that expensive. The expensive part is recursively updating a large tree very often. If you can “cut off” fast updates at reasonable depth it’s fine for some components to always render.
It’s hard to offer more help without a specific example you’re struggling with.
@gaearon
I agree with you that our optimizations may be extreme, but assuming that we have decided we want this optimization. and we have code like:
<ComponentWithChildren>
<span> this.props.text </span>
</ComponentWithChildren>
we don't want ComponentWithChildren
's children
prop to always change and cause a re-render, but only be recreated when this.props.text
changes
as I said. one solution was
// on class
renderChildren = () => <span> this.props.text </span>
// in render
<ComponentWithChildren renderChildren={this.renderChildren} />
Like I said however, I dislike this approach because its much less readable.
I was wondering if you have any other solutions or suggestions?
we don't want ComponentWithChildren's children prop to always change and cause a re-render, but only be recreated when this.props.text changes
If you just pass
<ComponentWithChildren>
{this.props.text}
</ComponentWithChildren>
(with no span), that's exactly what would happen.
I don’t think the approach you suggested works:
// on class
renderChildren = () => <span> this.props.text </span>
// in render
<ComponentWithChildren renderChildren={this.renderChildren} />
In this case even if this.props.text
changes, ComponentWithChildren
doesn’t know, and forgets to re-render. It would re-render if you also happen to pass text
itself as a prop:
<ComponentWithChildren renderChildren={this.renderChildren} text={this.props.text} />
But at this point you’re just duplicating information, and you might as well create <SpecificComponentWithChildren text={this.props.text} />
that is also a PureComponent
and that renders <ComponentWithChildren><span>{this.props.text}</span></ComponentWithChildren>
.
@gaearon
Your right about the function solution not working, thanks for that
That example was simple but the point is that I may have a more complex structure that can't be represented with a primitive. Is there no good way of solving this?
a real scenario from our code:
<SmallContactContainer
userId={status === 'Reassigned' ? originalOwnerId : ownerId}
retrieveQueue
renderChildren={this.renderSmallContactContent}
>
<div className={classes.dataItem}>
{this.getStatusText()}
{this.renderNewOwnerForReassigned()}
</div>
</SmallContactContainer>
Does what I suggested above in the last paragraph work for you? Just create a new component that encapsulates this render function, and make it a PureComponent
.
I thought you were saying to encapsulate the children into a component, but yeah I guess thats a good solution, thanks :)
@acdlite As you wrote, <Children/> === <Children/>
evaluates to false as it's actually a new instance of Children
and the render of parent component is called, but what happens with the <Children/>
component's mounting? It doesn't get remounted, right?
@ackvf
The
So, the instance reference changes, but not a single lifecycle method is called on the new object, not even the constructor. What am I missing? _(I haven't gone through the code yet - I find that difficult to start with.)_
@ackvf Not even the constructor, no. Since react works with lightweight nodes that just points to which function/tag/class should be invoked/newed... SHOULD the diffing decide it's a new element.
So when <Children/> !== <Children/>
, but the output is same, it means that new _Element_ instance is used (as in React.createElement(...)
, but both instances internally point to same class instance so that no lifecycle methods or constructor need to be called?
In other words, when pseudo-transpiled:
React.createElement(Children, ...) !== React.createElement(Children, ...)
where Children === Children
?
Then if my understanding is correct, both functions refer to the _class Children object_ and only internally they may call the new
 operator.
@gaearon Is that mean PureComponent
is useless for almost all Component with Children.
Found this thread while searching for info on memoizing components that use children. This thread confirms that children are a different reference each render, even if props haven't changed.
@vipcxj That's what I've pretty much concluded. But, what I've done to solve this is to use React.memo
with a 2nd argument comparison function like so (this is a little different as I'm not using classes, but the idea is the same):
// A component that uses children that you want to memoize.
const SomeComponent = ({ children }) => {
return (
<div>
{children}
</div>
)
}
// https://mzl.la/2LP6mjP
// Comparison function to JSON.stringify that can handle
// circular references and ignores internal React properties.
const circular = () => {
const seen = new WeakSet()
return (key, value) => {
if (key.startsWith('_')) return // Don't compare React's internal props.
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) return
seen.add(value)
}
return value
}
}
// Export a memoized version of the component
// using React.memo's 2nd compare function argument.
export default React.memo(SomeComponent, (prevProps, nextProps) => {
const prev = JSON.stringify(prevProps, circular())
const next = JSON.stringify(nextProps, circular())
return prev === next
})
So now, wherever you use SomeComponent
with children, if the children don't change, SomeComponent
won't re-render :)
@qodesmith It Works Well! But does this flow (without your fix) is very bad for performance? it basically means that we shouldn't use children as props because no matter what it will cause rerendering and painting on the screen because every time it creates a new element.
@yoni-wibbitz I'm not sure about the performance, but as @gaearon mentioned above, render
is actually pretty cheap, so I wouldn't worry about that. Also mentioned above is the fact that even if React calculates changes and renders, if it results in no DOM changes having to be made, then the DOM is in fact left alone, which leads me to believe no repainting happens.
Most helpful comment
This is the expected behavior.
The reason
ComponentWithChildren
re-renders every time is because<Children /> === <Children />
always evaluates to false. This may seem unintuitive, but it's because<Children />
is sugar forReact.createElement(Children)
, which returns a new object every time it is called.To fix, you can cache the
<Children />
element to ensure that the value is preserved across renders: