1
votes

I have a very heavy (computationally) functional component (Parent) which doesn't have a state and has few Child sub-components with local state. Children are dependent only on props send from the Parent.

I pass a function to one of the children (ChildA) to change the value of a variable on the Parent.

This variable is one of the props of a different Child component (ChildB) which has a state based on that prop and updates it in useEffect hook.

The ChildB component does not re-render when the value passed as prop changes on the Parent component.

Sure, introducing state (useState hook) on Parent fixes this but re-renders the parent over and over and kills the performance as Parent has 500+ nested components which all get re-rendered.

Introducing some kind of a Store (Redux, MobX) would probably solve the issue but that would be an overkill.

A simplified example:

import React, { useEffect, useState } from "react";

export default function App() {
  return <Parent />    
}

const ChildA = ({ onAction }) => {
  return <button onClick={onAction}>CLICK</button>;
};

const ChildB = ({ coreValue }) => {
  const [value, setValue] = useState(0);

  useEffect(() => {
    setValue(coreValue);
  }, [coreValue]);

  return <div>My value: {value}</div>;
};

const Parent = () => {
  let calculatedValue = 0;

  const changeValue = () => {
    calculatedValue += Math.random();
  };

  return (
    <div>
      <ChildA onAction={changeValue} />
      <ChildB coreValue={calculatedValue} />
    </div>
  );
};

You can test the code here: https://codesandbox.io/s/vigilant-wave-r27rg

How do I re-render only ChildB on props change?

2

2 Answers

0
votes

You have to store the value in parent component state and just send the parent component state value to the ChildB, there you don't need to maintain state and useEffect hook to catch the change. See the code here: https://codesandbox.io/s/adoring-chatelet-sjjfs

import React, { useState } from "react";
export default function App() {
  return <Parent />;
}
const ChildA = ({ onAction }) => {
  return <button onClick={onAction}>CLICK</button>;
};
const ChildB = ({ coreValue }) => {
  return <div>My value: {coreValue}</div>;
};
const Parent = () => {
  const [value, setValue] = useState(0);
  const changeValue = () => {
    setValue(value + Math.random());
  };
  return (
    <div>
       <ChildA onAction={changeValue} />
       <ChildB coreValue={value} />
    </div>
  );
};
0
votes

React's useCallback and memo prevent's unnecessary re-rendering. Note that ChildA doesn't re-render regardless of the number of times the Parent or ChildB state's changes. Also, your current example doesn't need useState / useEffect in ChildB

https://codesandbox.io/s/usecallback-and-memo-ptkuj

import React, { useEffect, useState, memo, useCallback } from "react";

export default function App() {
  return <Parent />;
}

const ChildA = memo(({ onAction }) => {
  console.log("ChildA rendering");
  return <button onClick={onAction}>CLICK</button>;
});

const ChildB = memo(({ coreValue }) => {
  const [value, setValue] = useState(0);

  useEffect(() => {
    setValue(coreValue);
  }, [coreValue]);

  return <div>My value: {value}</div>;
});

const Parent = () => {
  const [calculatedValue, setCalculatedValue] = useState(0);

  const changeValue = useCallback(() => {
    setCalculatedValue(c => (c += Math.random()));
  }, []);

  return (
    <div>
      <ChildA onAction={changeValue} />
      <ChildB coreValue={calculatedValue} />
    </div>
  );
};