I came across this behaviour recently and was trying to understand its cause. Basically, what I noticed so far is that a React child component will be mounted and unmounted on state changes of its parent. However, a jsx containing the same child component does not.
I put together this simplified example to demonstrate the behaviour.
const Child = ({ title }) => {
const [count, setCount] = React.useState(0);
const increment = () => setCount((x) => x 1);
return (
<button onClick={increment}>
{title} Current count = {count}
</button>
);
};
const App = () => {
const [, setState] = React.useState(false);
const rerender = () => setState((x) => !x);
const ChildWrapper = () => <Child title="Component" />;
const childWrapperJsx = <Child title="jsx" />;
return (
<div>
<button onClick={rerender}>Re render parent</button>
<br />
<ChildWrapper />
{childWrapperJsx}
</div>
);
}
const domContainer = document.querySelector('#root');
const root = ReactDOM.createRoot(domContainer);
const e = React.createElement;
root.render(e(App));
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<div id="root"></div>
Does anyone know the reason behind this? Is there a way to prevent React from unmounting a child component in this case?
CodePudding user response:
I think your question is about the difference between the two buttons' behavior, one is coming back to zero after clicking to rerender the parent component and the other not.
First of all, we should understand the life-cycle of a function component, the render is executing each state change.
function App() {
/**
* it will be executed each render
*/
return (<div />);
}
Also we have to understand the difference between create a component and instantiate a component.
// this is creating a component
const ChildWrapper = () => <Child title="Component" />;
// this is instantiating a component
const childWrapperJsx = <Child title="jsx" />;
JSX is only a tool to transpile the syntaxe of this <Child title="jsx" />
to React.createElement('div', { title: "jsx" })
for example. To explain better, the code is transpiled to something like this:
// this is creating a component
const ChildWrapper = () => React.createElement('button', { title: 'Component' });
// this is instantiating a component
const childWrapperJsx = React.createElement('button', { title: 'jsx' }) ;
Without going deep in the hole. In your implementation we have both components implemented into the render of the parent, like this.
function App() {
const ChildWrapper = () => <Child title="Component" />;
const childWrapperJsx = <Child title="jsx" />;
return (<div />);
}
Right now we realized that the first implementation is creating a new component each render, so that react can't memoize the component in the tree, it is not possible to do it.
// each render ChildWrapper is a new component.
const ChildWrapper = () => <Child title="Component" />;
And the second one, the childWrapperJsx
is already a react element instantiated and memoized. React will preserve the same instance on the parent component life cycle.
According to React's best practice, it is not recommended to create components inside the render of another component. If you try to put both implementations outside of the component, you will be able to see that both components won't be unmounted after the parent component's render.
const ChildWrapper = () => <Child title="Component" />;
const childWrapperJsx = <Child title="jsx" />;
function App() {
return (
<>
<ChildWrapper />
{childWrapperJsx}
</>
);
}