Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prevent child component from unmounting and remounting if parent component changes

Problem:

In the code below, when the Toggle ParentA/B button is clicked, the <Child /> component will be unmounted and remounted again, only though effectively only the Parent component changed.

How do I prevent Child from unmounting and remounting in such a situation?

import React, { useState } from "react"

const App = () => {
    const [a, setA] = useState(true)
    return (
        <div>
            <button onClick={() => setA(!a)}>Toggle ParentA/B</button>
            {a ? <ParentA><Child /></ParentA> : <ParentB><Child /></ParentB>}
        </div>
    )
}
const ParentA = ({ children }) => <div>parentA{children}</div>
const ParentB = ({ children }) => <div>parentB{children}</div>

class Child extends React.Component {
    componentDidMount() {
        console.log('child did mount')
    }

    componentWillUnmount() {
        console.log('child will unmount')

    }

    render() {
        return (
            <div>child</div>
        )
    }

}

export default App

Replies to potential answers:

Why not just let the Child component remount since it's only a <div> element?

Normally this wouldn't matter if the Child component renders cheaply. However, in my case, the Child component takes very long to mount, so I cannot afford a remounting every time the Parent component changes.

You could pass the parentA or parentB string as props to a generic Parent component.

const App = () => {
    const [a, setA] = useState(true)
    return (
        <div>
            <button onClick={() => setA(!a)}>Toggle ParentA/B</button>
            <ParentGeneric content={a? 'parentA': 'parentB'}><Child /></ParentGeneric>
        </div>
    )
}
const ParentGeneric = ({children, content}) => <div>{content}{children}</div>

class Child extends React.Component {
    ...
}

This would work. Unfortunately, this restricts my Parent component's JSX structure to be identical. In other words, if my ParentA and ParentB's JSX structure were different, then I'm not sure how to pass the difference as props.

For example:

const ParentA = ({ children }) => <div><div><h1>parentA{children}</h1></div></div>
const ParentB = ({ children }) => <div>parentB{children}</div>

If parentA and parentB are defined this way, would it still be possible to abstract these two components into a ParentGeneric component and simply passing the structural difference between ParentA and ParentB as props?

like image 982
t.c Avatar asked Aug 12 '19 07:08

t.c


People also ask

Does child component Rerender on parent state change?

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.

How do you stop components from unmounting?

Using react-router you can easily prevent route change(which will prevent component unmount) by using Prompt . You need to manually pass the getUserConfirmation prop which is a function. You can modify this function as you like in any Router(Browser, Memory or Hash) to create your custom confirmation dialog(eg.

Does child component Rerender when props change?

This doesn't only mean the component's render function will be called, but also that all its subsequent child components will re-render, regardless of whether their props have changed or not.

Does component remount when props change?

To remount a component when a prop changes, use the React key attribute as described in this post on the React blog: When a key changes, React will create a new component instance rather than update the current one. The example below shows how the key attribute can be used.


2 Answers

This isn't much in the way of a technical answer but there are reparenting libraries out there which can supposedly accomplish this: react-reverse-portal and react-reparenting.

I looked into it myself recently but they are somewhat experimental and it seemed like a lot of overhead to worry about. The answer from @deckele looks like a much better solution if you don't literally need to move the component across multiple branches.

like image 139
lawrence-witt Avatar answered Oct 01 '22 23:10

lawrence-witt


It's impossible, and clearly defined here (emphasis mine):

There are some generic solutions to this algorithmic problem of generating the minimum number of operations to transform one tree into another. However, the state of the art algorithms have a complexity in the order of O(n3) where n is the number of elements in the tree.

If we used this in React, displaying 1000 elements would require in the order of one billion comparisons. This is far too expensive. Instead, React implements a heuristic O(n) algorithm based on two assumptions:

  1. Two elements of different types will produce different trees.

  2. The developer can hint at which child elements may be stable across different renders with a key prop.

In practice, these assumptions are valid for almost all practical use cases.

If React would've diffed children of different parent types it would've slowed down the whole conciliation process by magnitudes.

So you're left with your proposed solution of a generic parent with props.

like image 27
Mordechai Avatar answered Oct 01 '22 22:10

Mordechai