Waleed93
Waleed93

Reputation: 1300

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

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>
   ); 
};

Upvotes: 2

Views: 3007

Answers (2)

Shubham Khatri
Shubham Khatri

Reputation: 281616

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" />

Upvotes: 2

ilkerkaran
ilkerkaran

Reputation: 4344

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>
   );
};

Upvotes: 0

Related Questions