There are two helpers that can be used to add content while rendering:
...
const DisplayA = () => <div className={'containerA'}>
<button onClick={handleToggleA}>{"A toggled: " + toggledA.toString()}</button>
</div>
const displayB = () => <div className={'containerB'}>
<button onClick={handleToggleB}>{"B toggled: " + toggledB.toString()}</button>
</div>
return (
<>
<DisplayA />
{ displayB() }
</>
);
...
The problem is that in the first helper, React always discards the entire subtree and creates it again from scratch, as can be seen here:
The demo
I know, the first way is syntax sugar for React.createElement so a new component is created each render. However, the second way, a distinct arrow function is created each render too.
Why does React not know how to reuse the subtree the first way but does know the second way? What is happening under the hood?
How can we spot when the DOM subtree is discarded and recreated each render? Is it enough to assume that one should not create inline components, and use inline functions only?
Notice, that helpers can come from props, for example (render props pattern).
When the state of a component changes, React updates the virtual DOM tree. Once the virtual DOM has been updated, React then compares the current version of the virtual DOM with the previous version of the virtual DOM. This process is called “diffing”.
React uses virtual DOM to enhance its performance. It uses the observable to detect state and prop changes. React uses an efficient diff algorithm to compare the versions of virtual DOM. It then makes sure that batched updates are sent to the real DOM for repainting or re-rendering of the UI.
Unmounting. The next phase in the lifecycle is when a component is removed from the DOM, or unmounting as React likes to call it. React has only one built-in method that gets called when a component is unmounted: componentWillUnmount()
React provides a declarative API so that you don't have to worry about exactly what changes on every update. This makes writing applications a lot easier, but it might not be obvious how this is implemented within React.
This will depend in which scope DisplayA
is defined. Functional components should usually be defined at the top level of a file. In your demo DisplayA
is a component that is created inside the render
of App
, so every time App
renders a new functional component is created, not a new invocation of the same component.
To resolve this make DisplayA
top level in the file and pass props through to it.
const DisplayA = ({handleToggle, toggled}) => <div className={'containerA'}>
<button onClick={handleToggle}>{"A toggled: " + toggled.toString()} </button>
</div>
const App = () => {
...
return <>
<DisplayA handleToggle={() => {...}} toggle={...} />
</>
}
The second approach does not create a component which is passed to react for reconciliation, but is a function which is invoked while rendering and puts the contained elements into the rendering of this component.
In the first case you are not calling DisplayA
. Instead, you are letting react decide when to render it. Notice how when transpiled, React.createElement(DisplayA)
does not invoke this function. When react renders the subtree, it decides what needs to be re-rendered.
The process of handling tree changes/updates is called reconciliation. In react docs it says that same types will try to maintain state, whereas different component types will always perform a tear down on the DOM tree.
The latter happens with your DisplayA
component because it's a different value on every render. Although the component renders the same view, React can't be sure that this is the same component because the value DisplayA
points to a different component reference each time. In this case you are using function components, therefore the value is a reference to a new function. The variable name just happens to be the same - it does not have any importance at run time.
In the second case with displayB
you are explicitly calling the function and rendering its result. Because the function is pure, this is equivalent to pulling its return value and inlining it inside the parent component:
return (
<>
<DisplayA />
{
<div className={'containerB'}>
<button onClick={handleToggleB}>{"B toggled: " + toggledB.toString()}</button>
</div>
}
</>
)
Notice how the second child of this fragment is now a div
. Because div
s are primitive elements, they are represented by the literal string 'div'
and not a reference to a component. React knows between renders that this is the same tree and as such does not destroy it. This would also work if you had any external component with a stable reference - it will be considered an element of the same type.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With