'How to pass data from child component to parent component without the child component rerendering in React?

My child component is an accordion with controls in it. The problem is, everytime you input data on the controls and it triggers an onChange event which passes its data to its parent component, the accordion closes/collapses and I don't need that behavior.

The accordion is just imported from a library and I can't change its behavior.

Is there anyway to prevent this or pass props/data from child component to parent component without rerendering/restarting DOM of child component?

Parent component:

  const ParentComponent = () => {

  const handleChangeChildAccordion = (e) => {
    console.log(e);
  }

  return (
    <ChildComponentAccordion currentData={currentData} onChangeTPAccordion={handleChangeChildAccordion}/> }
  );
}

Child Component:

const ChildComponent = (props) => {

  const onDataChangeHandler = (e) => {
       props.onChangeTPAccordion(e);
    };

    return (
      <Accordion onChange={onDataChangeHandler}/>
    );
}


Solution 1:[1]

The child component doesn't remount, it rerenders. Normally, a component rerenders any time any its parent rerenders. To avoid that you:

  • Avoid changing the child component's props, and
  • Memoize the child component so it doesn't rerender when its props don't change in a way it cares about.

You do the first by ensuring that, for instance, callbacks you pass the component are stable (they don't change on every render). In a function component, you do it by using useMemo, useCallback, and/or useRef. In a class component, you typically do that by making the callbacks properties on the component instance that aren't recreated.

You do the second by using React.memo (if it's a function component) or for a class component by extending PureComponent (for simple memoization) or implementing shouldComponentUpdate.

You haven't given us much code to work with, so here's a simple example:

const { useState, useCallback, useRef } = React;

const log = (...msgs) => console.log(...msgs);

const Child1 = React.memo(({value, doUpdate}) => {
    log(`Child1 rendering`);
    return <div>
        Child1 - Value: {value} <input type="button" value="+" onClick={doUpdate} />
    </div>;
});

const Child2 = React.memo(({value, doUpdate}) => {
    log(`Child2 rendering`);
    return <div>
        Child2 - Value: {value} <input type="button" value="+" onClick={doUpdate} />
    </div>;
});

const Parent = () => {
    log(`Parent rendering`);
    const [counter1, setCounter1] = useState(0);
    const [counter2, setCounter2] = useState(0);

    // `useCallback` remembers the first function you pass it and returns
    // it to you, only returning the new one you pass it if the
    // dependencies array changes. This is one way to create a stable
    // callback.
    const update1 = useCallback(() => setCounter1(c => c + 1), []);

    // `useRef` gives you an object you can put arbitrary properties on,
    // which is another way to have a stable callback.
    const update2Ref = useRef(null);
    if (!update2Ref.current) {
        update2Ref.current = () => setCounter2(c => c + 1);
    }
    const update2 = update2Ref.current;

    return <div>
        <Child1 value={counter1} doUpdate={update1} />
        <Child2 value={counter2} doUpdate={update2} />
    </div>;
};

ReactDOM.render(<Parent />, document.getElementById("root"));
<div id="root"></div>

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

TypeScript playground with types added

That shows two different ways of making acallback stable (useCallback and useRef). (useMemo is what useCallback uses under the hood, so I didn't do an example of it.)

In that example, notice how the child telling the parent to update its counter doesn't cause the other child to re-render. That's because:

  • We don't change the other child's props (because our callbacks are stable and the other child's counter didn't change), and
  • We've memoized the child components

You can customize whether the component re-renders by passing a second argument to React.memo which is a function that says whether the new props coming in are "the same" (for rendering purposes) as the previous ones. By default, React.memo does a simple === comparison on each prop. And again, for class components, PureComponent does the simple === comparison, or you can implement shouldComponentUpdate yourself for more finely-tuned checks.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1