I’ve read a gazillion articles about renders and composition in React, sometimes with contradicting information. At some point I thought I got it, when I read that if React "still has the same children prop it got from the App last time, React doesn’t visit that subtree."
In the code below I am triggering a re-render of App
every 100ms by setting state, and React outputs "rerendered" in the console every 100ms.
Why does Child
get re-rendered at each commit? Am I not "lifting content up" here?
import { useState, useEffect } from 'react';
const App = () => {
const [now, setNow] = useState()
// Start a timer
useEffect(() => {
const interval = setInterval(() =>
setNow(Date.now()), 100)
return () => clearInterval(interval)
}, [])
return (
<div className="App">
<Parent>
<Child />
</Parent>
</div>
)
}
export default App
const Parent = ({ children }) => {
return (
<div id='parent'>
{children}
</div>
)
}
const Child = () => {
console.log('rendered')
return (
<p>whatever</p>
)
}
const { useEffect, useState } = React;
const Child = () => {
console.log('rendered')
return (
<p>whatever</p>
)
}
const Parent = ({ children }) => {
return (
<div id='parent'>
{children}
</div>
)
}
const App = () => {
const [now, setNow] = useState()
// Start a timer
useEffect(() => {
const interval = setInterval(() =>
setNow(Date.now()), 100)
return () => clearInterval(interval)
}, [])
return (
<div className="App">
<Parent>
<Child />
</Parent>
</div>
)
}
ReactDOM
.createRoot(document.getElementById("root"))
.render(<App />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>
2
Answers
You can memoize the
Parent
–Child
portion inside ofApp
. Since the state ofApp
is updated every 100ms, it will re-render every 100ms top-down. If you have elements that do not need thenow
value as a prop, you will need to use memoization.Note: I also moved your timer logic into a hook.
This is happening because you are creating a new
<Child />
component everytime the state in the<App/>
component is updated. So the statement that "if React still has the same children prop it got from the App last time, React doesn’t visit that subtree" is no longer true. If you move your timer from<App/>
to<Parent/>
component you can see in the snippet below that the<Child/>
will no longer rerender because it is only created once in the<App/>
and passed down as children prop.