Children in Parent Functional Component are not Re-rendering on Props Change

2.9k views Asked by At

I'm dynamically generating children components of HOC parent (see below). I pass the props directly to one of children and set the prop in it. I expect to see child re-rendering on props change but it doesn't.

Is the code incorrect somewhere?

ParentComponent

...
const ParentComponent = ({children}) => {
   const [state1, setState1] = useState(true);

   ...

   const changeOpacity = event => setState1(!state1);

   const renderChildren = React.useCallback(() => React.Children.toArray(children).map((child, index) => (
      <div key={index} style={{opacity: `${state1 ? 0 : 1}`}}>
         {child}
      </div>
   )), [state1]);

   return (
      <div>
         <Button onClick={changeOpacity}>Toggle Opacity</Button>
         {renderChildren()}
      </div>
   );
};

App.js

...
const App = () => {
   const [prop1, setProp1] = useState(123);

   return (
      <ParentComponent>
         <Child1 prop1={prop1} setProp1={setProp1} />
         <Child2 />
      </ParentComponent>
   ); 
};
2

There are 2 answers

2
Shubham Khatri On BEST ANSWER

In your ParentComponent, the children are cloned and then used to render as a part of the return value from the renderChildren function. Since the logic to compute children is not run on change of props to children, your child component is not affected by a change in its prop.

You can add children dependency to useCallback and it will work fine.

const { useState, useCallback } = React;
const ParentComponent = ({children}) => {
   const [state1, setState1] = useState(true);


   const changeOpacity = event => setState1(!state1);

   const renderChildren = useCallback(() => React.Children.map(children, (child, index) => (
      <div key={index} style={{opacity: `${state1 ? 0 : 1}`}}>
         {child}
      </div>
   )), [children, state1]);

   return (
      <div>
         <button onClick={changeOpacity}>Toggle Opacity</button>
         {renderChildren()}
      </div>
   );
};
const Child1 = ({prop1, setProp1}) => <div>{prop1} <button onClick={() => setProp1(234)}>Click</button></div>;
const Child2 = () => <div>Hello</div>
const App = () => {
   const [prop1, setProp1] = useState(123);

   return (
      <ParentComponent>
         <Child1 prop1={prop1} setProp1={setProp1} />
         <Child2 />
      </ParentComponent>
   ); 
};

ReactDOM.render(<App />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="app" />

0
ilkerkaran On

Is there anything prevent you from the approach below;

const ParentComponent = ({children}) => {
   const [state1, setState1] = useState(true);

   ...

   const changeOpacity = event => setState1(!state1);

   const renderChildren = useCallback(() => React.Children.toArray(children).map((child, index) => (
      <div key={index}>
         {child}
      </div>
   )), [children]);

   return (
      <div>
         <Button onClick={changeOpacity}>Toggle Opacity</Button>
         {state1 && renderChildren()}
      </div>
   );
};