-
Notifications
You must be signed in to change notification settings - Fork 47.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Children prop gets recreated killing PureComponent optimizations #8669
Comments
This is the expected behavior. The reason To fix, you can cache the 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. |
If props are the same every time, you can hoist If it receives different props every time then it’s not “the same” on updates. Therefore it makes sense for |
@gaearon thanks for chiming in, were having a lot of trouble with optimizing these cases. One approach we have taken is passing a 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) |
You may be optimizing for the wrong thing. Running It’s hard to offer more help without a specific example you’re struggling with. |
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:
we don't want as I said. one solution was
Like I said however, I dislike this approach because its much less readable. I was wondering if you have any other solutions or suggestions? |
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 <ComponentWithChildren renderChildren={this.renderChildren} text={this.props.text} /> But at this point you’re just duplicating information, and you might as well create |
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:
|
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 |
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, |
The instance reference changed but its type (and key) is still the same, so the underlying DOM Element needs no update and the same component remains mounted. |
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 In other words, when pseudo-transpiled: Then if my understanding is correct, both functions refer to the class Children object and only internally they may call the |
@gaearon Is that mean |
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 // 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 |
@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, |
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:
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
The text was updated successfully, but these errors were encountered: