Home > Net >  React sibling component updates state of parent causing re-render
React sibling component updates state of parent causing re-render

Time:09-28

I'll preface this question by saying I've spent about 3 weeks with React (previously have worked with Vue) so still pretty green.

I have a component structure like the following:

const initialState = { propertyA: null, propertyB: null };

const WrapperComponent: FC<Props> = (props) => {

  const [dynamicObject, setDynamicObject] = React.useState(initialState);
  const customCallbackFunction = (newObjectVal) => { setDynamicObject(newObjectVal) };

  return (
    <div>
      <SiblingComponent dynamicObject={dynamicObject} />
      <DifferentSiblingComponent onAction={customCallbackFunction} />
    </div>
  );
}  

Problem I'm facing is each call to customCallbackFunction is triggering re-render in both SiblingComponent and DifferentSiblingComponent. The re-render in SiblingComponent is desired, because I want that component to display the dynamic data being emitted by customCallbackFunction. However, I'd like to avoid the re-render of DifferentSiblingComponent.

For more context, customCallbackFunction is being fired on certain hover events on a canvas - so the constant re-rendering is causing an infinite callback loop.

Is there a way to handle this without pulling in something like Redux? Any help/insight is appreciated.

Note: I did read that React.FC is discouraged, that is what the team has used in the past so I was just following those templates

CodePudding user response:

When a parent rerenders all of its children are rerendered also. To prevent that React.memo might be helpful.

const MyComponent = React.memo(function DifferentSiblingComponent(props) {
  /* .... */
});

After you apply memo to one of your components you may need to apply useCallback to the customCallbackFunction so that it is not different during rerenders and doesn't cause memo to rerender the component anyway. e.g.

let customCallbackFunction = React.useCallback((newObjectVal) => { setDynamicObject(newObjectVal) },[])

CodePudding user response:

Problem I'm facing is each call to customCallbackFunction is triggering re-render in both SiblingComponent and DifferentSiblingComponent.

Yes, that's normal. Unless you do something to prevent it, all child components of the component whose state was updated are updated.

To prevent that in your situation, you need to do two things:

  1. Memoize DifferentSiblingComponent. Since it's a function component, you'd do that with React.memo.

  2. Memoize customCallbackFunction with useCallback or useMemo or similar so that it's stable across the lifecycle of the parent component, rather than being newly created every time the component renders. That way, DifferentSiblingComponent sees a stable value, and the memoization works.

    const customCallbackFunction = useCallback(
        (newObjectVal) => { setDynamicObject(newObjectVal) },
        []
    );
    

I go into more detail on this in this answer.

Live Example:

const { useState, useCallback } = React;

const initialState = { propertyA: null, propertyB: null };

const SiblingComponent = React.memo(({ dynamicObject }) => {
    console.log(`SiblingComponent rendered`);
    return <div>{JSON.stringify(dynamicObject)}</div>;
});

// Just so we can see things change
let counter = 0;

const DifferentSiblingComponent = React.memo(({ onAction }) => {
    console.log(`DifferentSiblingComponent rendered`);
    return (
        <input
            type="button"
            onClick={() => onAction({ ...initialState, counter:   counter })}
            value="Update"
        />
    );
});

const WrapperComponent /*: FC<Props>*/ = (props) => {
    const [dynamicObject, setDynamicObject] = useState(initialState);
    const customCallbackFunction = useCallback((newObjectVal) => {
        setDynamicObject(newObjectVal);
    }, []);

    return (
        <div>
            <SiblingComponent dynamicObject={dynamicObject} />
            <DifferentSiblingComponent onAction={customCallbackFunction} />
        </div>
    );
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<WrapperComponent />);
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>

  • Related