skip to Main Content

Is there anyway in React to prevent the Widget component be unmounted and mounted again when we change the condition prop?

interface FooProps {
  condition:boolean
}

const Foo: React.FC<FooProps> = ({condition}) => {
  const widget = <Widget/>
  
  if(condition){
    return <ComponentA>{widget}</ComponentA>
  }
  
  return (
    <ComponentB>
      <ComponentC/>
      {widget}
    </ComponentC>
  )
}

2

Answers


  1. For this, you need to do memoization. You will have to use React.memo for this.

    import React from 'react';
    
    interface WidgetProps {
      // Props for your Widget component
    }
    
    const Widget: React.FC<WidgetProps> = () => {
      // Your Widget component logic
      return <div>Widget</div>;
    };
    
    // Memoize the Widget component
    const MemoizedWidget = React.memo(Widget);
    
    interface FooProps {
      condition: boolean;
    }
    
    const Foo: React.FC<FooProps> = ({ condition }) => {
      // Use MemoizedWidget instead of Widget
      const widget = <MemoizedWidget />;
    
      if (condition) {
        return <ComponentA>{widget}</ComponentA>;
      }
    
      return (
        <ComponentB>
          <ComponentC />
          {widget}
        </ComponentB>
      );
    };
    

    Try this and see if its working as per your need. This will render the component only if the props has changed.

    Login or Signup to reply.
  2. It unmounts because the node does not exist in the Virtual DOM anymore. You can not prevent that.

    But…

    You can make some tricks to make it work

    You can hide it via css

    Instead of conditionally rendering the component, you can conditionally hide it via display: none. It will mount the component twice, but it is not gonna unmount between the rerenders if the condition changes

    Use common parent

    If the element has same parent between the renders it won’t unmount.

     if (condition) {
        return <ComponentA><Widget key="widget"/></ComponentA>;
      }
    
      return (
        <ComponentA>
          <ComponentC />
          <Widget key="widget" /> 
        </ComponentA>
      );
    

    <Widget key="widget" /> key is necessary. Since children from ComponentA compiles into array of react elements, using key prevents unmounting of individual elements if their order change. In the first condition Widget is the first element, in the second – the second element

    Use Portal

    You can attach Widget to the same node of the Virtual DOM and render it wherever you want using portals

    const Example = () => {
        const [s, ss] = React.useState(true)
        return (
            <>
                <button onClick={() => ss((o) => !o)}>click</button>
                <Foo condition={s} />
            </>
        )
    }
    
    const Widget = () => {
        React.useEffect(() => {
            console.log('Widget mount')
        }, [])
    
        return <>widget</>
    }
    
    const ComponentA = ({ children }: { children: React.ReactElement }) => {
        React.useEffect(() => {
            console.log('ComponentA mount')
        }, [])
    
        return <>{children} inside component a</>
    }
    
    const ComponentC = ({ children }: { children: React.ReactElement }) => {
        React.useEffect(() => {
            console.log('ComponentC mount')
        }, [])
    
        return <>{children} inside component c</>
    }
    
    const Foo = ({ condition }: { condition: boolean }) => {
        const ref = React.useRef<any>()
        let content
        if (condition) {
            content = (
                <ComponentA>
                    <div ref={ref} />
                </ComponentA>
            )
        } else {
            content = (
                <ComponentC>
                    <div ref={ref} />
                </ComponentC>
            )
        }
        return (
            <>
                {content}
                <Portal
                    dependency={[condition]}
                    elementRef={ref}
                >
                    <Widget />
                </Portal>
            </>
        )
    }
    
    const Portal = ({ elementRef, children, dependency = [] }: any) => {
        const el = React.useRef<any>()
        if (!el.current) {
            el.current = document.createElement('div')
        }
    
        React.useLayoutEffect(() => {
            const element = elementRef.current
            element.appendChild(el.current)
            return () => {
                element.removeChild(el.current)
            }
        }, dependency)  // we need to execute this again if the content changes
    
        return createPortal(children, el.current)
    }
    

    In this example <Widget />:

    • is a sibling to the content in the Virtual DOM
    • is a child of content in the real DOM

    In this example if you click the button it will change the content, but the Widget won’t unmount

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search