skip to Main Content

Here I have created a toggle button re-rendering in parent components and in child components a plus button, and the counter requirement is that the toggle button re-rendering is a toggle button that will hide and show the components, but if any last counter is added, for example, from 0 to 2, after hiding and showing it should remain the same, like 2 and not 0, but the problem is that I am not able to hide the child components, so how do I hide and show the child components and maintain the child state?
Basically, by rendering the parent, I want to keep maintaining the child state value.
parent code is

import React, { useRef, useState, useMemo } from 'react';
import DropDown from './DropDown';

function App() {
  const shouldRenderChild = useRef(true);
  const [hide, setHide] = useState(false);

  const toggleChildRendering = () => {
    shouldRenderChild.current = !shouldRenderChild.current;
    setHide(!hide);
  };

  const MemoizedDropDown = useMemo(() => {
    return shouldRenderChild.current ? <DropDown /> : null;
  }, []);

  return (
    <div>
      <button onClick={toggleChildRendering}>
        Toggle Child Rendering
      </button>
      {MemoizedDropDown}
    </div>
  );
}

export default App;

child code is

import React, { useState } from "react";
export default function DropDown() {
  const [number, setNumber] = useState(0);
  return (
    <>
      <button onClick={() => setNumber(number + 1)}>+</button>
      <div>Child Component{number}</div>
    </>
  );
}

3

Answers


  1. The main issue with your approach is that you’re using useMemo in the wrong way. useMemo caches a calculated value (in this case, a component) and will only recompute this value if one of its dependencies changes. You should use useMemo to decide whether or not to render the DropDown component, and as a dependency for that decision, use hide.

    Here’s a more detailed solution:

    Parent Component:
    We’ll use hide as the state to determine if we should show the child. We’ll use useMemo to cache the child component. If hide changes, then it will recompute whether to show the child component.

    import React, { useState, useMemo } from 'react';
    import DropDown from './DropDown';
    
    function App() {
      const [hide, setHide] = useState(false);
    
      const toggleChildRendering = () => {
        setHide(!hide);
      };
    
      const MemoizedDropDown = useMemo(() => {
        return !hide ? <DropDown /> : null;
      }, [hide]);
    
      return (
        <div>
          <button onClick={toggleChildRendering}>
            Toggle Child Rendering
          </button>
          {MemoizedDropDown}
        </div>
      );
    }
    
    export default App;
    

    Child Component:
    This remains the same. It simply shows a button to increment a counter.

    import React, { useState } from "react";
    
    export default function DropDown() {
      const [number, setNumber] = useState(0);
      return (
        <>
          <button onClick={() => setNumber(number + 1)}>+</button>
          <div>Child Component: {number}</div>
        </>
      );
    }
    

    By following this approach, you can show/hide the child component without it losing its internal state. However, note that if the child component gets unmounted (i.e., completely removed from the DOM) and then remounted, its internal state will be reset. If you want to persist the state even if the component is unmounted, you’ll need a more complex solution, such as using context or moving the state up to the parent component and passing it down as props.

    Login or Signup to reply.
  2. Every time component is mounted or unmounted (using conditional rendering) – state of the component is re-setting itself to default values. So that is your problem, and useMemo hook will not help you here.

    To solve your issue you can modify your root css files and add something like .hidden class:

    .hidden {
      display: none;
    }
    

    Most probably you already have that kind of utility classes in your project. Like d-none for bootstrap or similar.

    Next – you need to add a wrapper to your DropDown component, or change the type of root node that is returned by this component to something else than React.Fragment ie <> </> and to apply conditional className on it like this:

    className={hide ? 'hidden' : ''}
    

    Here is an example of the solution:

    import React, { useState } from 'react';
    
    function DropDown({ className }) {
      const [number, setNumber] = useState(0);
      return (
        <div className={className}>
          <button onClick={() => setNumber(number + 1)}>+</button>
          <div>Child Component: {number}</div>
        </div>
      );
    }
    
    export const App = () => {
      const [hide, setHide] = useState(false);
    
      const toggleChildRendering = () => {
        setHide(!hide);
      };
    
      return (
        <div>
          <button onClick={toggleChildRendering}>Toggle Child Rendering</button>
          <DropDown className={hide ? 'hidden' : ''} />
        </div>
      );
    };
    

    Stackblitz demo

    Login or Signup to reply.
  3. The simple solution to this is Lifting the State up.

    1. Basically, if you’re hiding a component with the condition !hide ? <DropDown /> : null, it’d be unmounted when hide is true.
    2. Now, when you’re storing the number in child’s state, it would also be vanished.
    3. So, if you store the number value in parent’s state, it’d persist.
    4. Now, if the child component gets unmounted due to the hide flag being true, you still got the state saved in parent.
    5. This way, you can hide/show the child component and the state would be preserved.
    6. I would not suggest you to hide the component with CSS. But if it’s a special use case for you then you can just use the hide flag for styling the child component to display: none

    <Dropdown style={{
    display: hide ? ‘none’ : ‘block’
    }} … />

    The right way:

    App.js

    function App() {
      const [number, setNumber] = useState(0);
      const [hide, setHide] = useState(false);
      ...
      return (
        ...
        <Dropdown number={number} changeNumber={setNumber} />
      )
    }
    

    Dropdown.js

    export default function DropDown({number, changeNumber}) {
      return (
        <>
          <button onClick={() => changeNumber(number + 1)}>+</button>
          <div>Child Component{number}</div>
        </>
      );
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search