React: Do children always rerender when the parent component rerenders?
I'll post your actual code for context:
class Application extends React.Component {
render() {
return (
<div>
{/*
Clicking this component only logs
the parents render function
*/}
<DynamicParent>
<Child />
</DynamicParent>
{/*
Clicking this component logs both the
parents and child render functions
*/}
<StaticParent />
</div>
);
}
}
class DynamicParent extends React.Component {
state = { x: false };
render() {
console.log("DynamicParent");
return (
<div onClick={() => this.setState({ x: !this.state.x })}>
{this.props.children}
</div>
);
}
}
class StaticParent extends React.Component {
state = { x: false };
render() {
console.log("StaticParent");
return (
<div onClick={() => this.setState({ x: !this.state.x })}>
<Child />
</div>
);
}
}
function Child(props) {
console.log("child");
return <div>Child Text</div>;
}
When you write this code in your Application render:
<StaticParent />
What's rendered is this:
<div onClick={() => this.setState({ x: !this.state.x })}>
<Child />
</div>
And in reality, what happens (roughly) is this:
function StaticParent(props) {
return React.createElement(
"div",
{ onClick: () => this.setState({ x: !this.state.x }) },
React.createElement(Child, null)
);
}
React.createElement(StaticParent, null);
When you render your DynamicParent like this:
<DynamicParent>
<Child />
</DynamicParent>
This is what actually happens (again, roughly speaking)
function DynamicParent(props) {
return React.createElement(
"div",
{
onClick: () => this.setState({ x: !this.state.x }),
children: props.children
}
);
}
React.createElement(
DynamicParent,
{ children: React.createElement(Child, null) },
);
And this is the Child in both cases:
function Child(props) {
return React.createElement("div", props, "Child Text");
}
What does this mean? Well, in your StaticParent component you're calling React.createElement(Child, null)
every time the render method of StaticParent is called. In the DynamicParent case, the Child gets created once and passed as a prop. And since React.createElement
is a pure function, then it's probably memoized somewhere for performance.
What would make Child's render run again in the DynamicParent case is a change in Child's props. If the parent's state was used as a prop to the Child, for example, that would trigger a re-render in both cases.
I really hope Dan Abramov doesn't show up on the comments to trash this answer, it was a pain to write (but entertaining)
does parent component re-renders when changes occur in child components?
No, it will not re-render. If you pass any props to the component from the parent component and you update that prop in children or that prop update in the parent component so both will re-render. But if the data or state has no dependency on the parent component so it will not cause a re-render in the parent component.
React: Parent component re-renders all children, even those that haven't changed on state change
If a parent component is updated, does React always update all the direct children within that component?
No. React will only re-render a component if shouldComponentUpdate()
returns true
. By default, that method always returns true
to avoid any subtle bugs for newcomers (and as William B pointed out, the DOM won't actually update unless something changed, lowering the impact).
To prevent your sub-component from re-rendering unnecessarily, you need to implement the shouldComponentUpdate
method in such a way that it only returns true
when the data has actually changed. If this.props.messages
is always the same array, it could be as simple as this:
shouldComponentUpdate(nextProps) {
return (this.props.messages !== nextProps.messages);
}
You may also want to do some sort of deep comparison or comparison of the message IDs or something, it depends on your requirements.
EDIT: After a few years many people are using functional components. If that's the case for you then you'll want to check out React.memo. By default functional components will re-render every time just like the default behavior of class components. To modify that behavior you can use React.memo()
and optionally provide an areEqual()
function.
Does react hook component re-render when parent component re-renders?
Yes, the standard behavior in react is that when a component renders, its children render too. To improve performance you can skip some renders with shouldComponentUpdate on a class component or React.memo on a functional component, but this is a performance optimization, not something you should rely on for skipping effects.
If you're fetching data in a component, you often only want to do it on mount, or only when certain relevant data changes. This is controlled with useEffect
's dependency array. To run it only on mount, provide an empty array []
. The component will still rerender when the parent component changes, but its effect will not rerun.
How does React re-use child components / keep the state of child components when re-rendering the parent component?
createElement
vs render
vs mountWhen a React Component such as your Button
is rendered, a number of children are created with createElement
. createElement(Timer, props, children)
does not create an instance of the Timer
component, or even render it, it only creates a "React element" which represents the fact that the component should be rendered.
When your Button
is rendered, react will reconcile the result with the previous result, to decide what needs to be done with each child element:
- If the element is not matched to one in the previous result, then a component instance is created then mounted then rendered (recursively applying this same process). Note that when
Button
is rendered for the first time, all of the children will be new (because there is no previous result to match against). - If the element is matched to one in the previous result, then the component instance is reused: its props are updated, then the component is re-rendered (again, recursively applying this same process). If the props did not change, React might even choose not to re-render as an efficiency.
- Any elements in the previous result that was not matched to an element in the new result will be unmounted and destroyed.
An element "matches" another one if React compares them and they have the same type.
The default way for React to compare children, is to simply iterate over both lists of children at the same time, comparing the first elements with each other, then the second, etc.
If the children have key
s, then each child in the new list is compared to the child in the old list that has the same key.
See the React Reconciliation Docs for a more detailed explanation.
ExamplesYour Button
always returns exactly one element: a button
. So, when your Button
re-renders, the button
matches, and its DOM element is re-used, then the children of the button
are compared.
The first child is always a Timer
, so the type matches and the component instance is reused. The Timer
props did not change, so React might re-render it (calling render
on the instance with the same state), or it might not re-render it, leaving that part of the tree untouched. Both of these cases would result in the same result in your case - because you have no side-effects in render
- and React deliberately leaves the decision of when to re-render as an implementation detail.
The second child is always the string "Clicks: "
so react leaves that DOM element alone too.
If this.state.click
has changed since the last render, then the third child will be a different string, maybe changing from "0"
to "1"
, so the text node will be replaced in the DOM.
If Button
s render
were to return a root element of a different type like so:
render() {
return createElement(this.state.clicks % 2 ? 'button' : 'a', { onClick: () => this.click() },
createElement(Timer, null),
'Clicks: ',
this.state.clicks
);
}
then in the first step, the a
would be compared to the button
and because they are different types, the old element and all of its children would be removed from the DOM, unmounted, and destroyed. Then the new element would be created with no previous render result, and so a new Timer
instance would be created with fresh state, and the timer would be back at 0.
Timer matches? | previous tree | new tree |
---|---|---|
no match | <div><Timer /></div> | <span><Timer /></span> |
match | <div>a <Timer /> a</div> | <div>b <Timer /> b</div> |
no match | <div><Timer /></div> | <div>first <Timer /></div> |
match | <div>{false}<Timer /></div> | <div>first <Timer /></div> |
match | <div><Timer key="t" /></div> | <div>first <Timer key="t" /></div> |
Prevent Child Rerendering if Parent is Rerendered Using Hooks
Since you are using useContext
, your component will always re-renders.
When the nearest <MyContext.Provider> above the component updates, this Hook will trigger a rerender with the latest context value passed to that MyContext provider. Even if an ancestor uses React.memo or shouldComponentUpdate, a rerender will still happen starting at the component itself using useContext.
Reference: https://reactjs.org/docs/hooks-reference.html#usecontext
I was trying to refactor your code using the 2nd strategy pointed from the docs: https://github.com/facebook/react/issues/15156#issuecomment-474590693.
However, I soon realized that the addToCart
function has cartItems
as its dependency, so whenever cartItems
changes, addToCart
changes and it's kind of impossible to avoid re-renders since every Product
component use addToCart
function.
That leads me to the use of useReducer
because React guarantees that its dispatch
is stable and won't change during re-renders.
So here's the working Codesandbox: https://codesandbox.io/s/red-feather-dc7x6?file=/src/App.js:786-797
Related Topics
How to Download Fetch Response in React as File
Print Function Not Working in Chrome
Angular: Toggle Text of Button Based on Boolean Value in Model
How to Clear Input After Onclick With Reactjs
Hiding the Address Bar of a Browser (Popup)
How to Check a Radio Button in the Table Row Based on That Column Header
Typeerror: Data.Foreach Is Not a Function
How to Access External Json File Objects in Vue.Js App
How to Merge Two Json Object Values by Id With Plain JavaScript (Es6)
How to Remove Forward and Backward Slashes from String in JavaScript
Prevent a White Space in the Beginning of a Input
Pass Dynamic Object in Onclick - Javascript/Jquery
Check If Image Exists on Server Using JavaScript
How to Execute a Function After Another One
How to Find Middle Element of Array in JavaScript
How to Manage Hide and Show Multiple Div and Sections Using Jquery