21

If a parent re-renders, children in React are also re-rendered, no matter if the passed props changed or not.

Why is React doing that? What would be the issue if React wouldn't re-render children (without changed props) when the parent renders?

Update: I am talking about this in the React Devtools profiler: enter image description here

Sample code:

App.tsx:

 import React, { useMemo, useState } from "react";
    import "./App.css";
    import { Item, MyList } from "./MyList";
    
    function App() {
      console.log("render App (=render parent)");
    
      const [val, setVal] = useState(true);
      const initialList = useMemo(() => [{ id: 1, text: "hello world" }], []); // leads to "The parent component rendered"
      //const initialList: Item[] = []; // leads to "Props changed: (initialList)"
    
      return (
        <div style={{ border: "10px solid red" }}>
          <button type="button" onClick={() => setVal(!val)}>
            re-render parent
          </button>
          <MyList initialList={initialList} />
        </div>
      );
    }
    
    export default App;

MyList.tsx:

import { FC, useState } from "react";

export interface Item {
  id: number;
  text: string;
}

interface Props {
  initialList: Item[];
  //onItemsChanged: (newItems: Item[]) => void;
}

export const MyList: FC<Props> = ({
  initialList,
  //onItemsChanged,
}) => {
  console.log("render MyList");

  const [items, setItems] = useState(initialList);

  return (
    <div style={{ border: "5px solid blue" }}>
      <ul>
        {items.map((item) => (
          <li key={item.id}>{item.text}</li>
        ))}
      </ul>
      <button type="button">add list item (to be implemented)</button>
    </div>
  );
};
stefan.at.kotlin
  • 15,347
  • 38
  • 147
  • 270
  • It’s not true that the children re-render. – Aaron Brager Jan 09 '21 at 14:39
  • @AaronBrager I added a screenshot from React Devtools and some sample code. It might not re-render the DOM, but it re-renders at least in React and I am wondering why is that. – stefan.at.kotlin Jan 09 '21 at 14:47
  • consider the whole `function` component as the `render` method of `class` component. putting `console.log` there makes the whole function render again – Hagai Harari Jan 09 '21 at 14:54
  • From what I've got from the documentation - people tend to mutate state, even if you ask them nicely not to. So instead of comparing props they just run render again and see if DOM need an update. They've added PureComponent, that does a shallow comparison of properties for cases like yours. (the point I was trying to make - props might be complex objects costly to compare) – Nadia Chibrikova Jan 09 '21 at 15:15
  • @HagaiHarari Can you elaborate more? I am new to React and not familiar with class components. However, in my Hooks version I commented out the `console.log` statements, but as expected, this does not change anything. – stefan.at.kotlin Jan 09 '21 at 15:40

6 Answers6

14

React achieves a fast and responsive UI by re-rendering components on every state change (using setState) or from changes of props, followed by React’s reconciliation diffing algorithm that diffs previous renders with current render output to determine if React should commit changes to the component tree (e.g. DOM) with the new updates.

However, unnecessary component re-renders will happen and can be expensive, It’s been a common performance pitfall in every single React project that I’ve been working on. SOURCE

Solution for this issue : A component can re-render even if its props don’t change. More often than not this is due to a parent component re-rendering causing the child to re-render.

To avoid this, we can wrap the child component in React.memo() to ensure it only re-renders if props have changed:

function SubComponent({ text }) {
  return (
    <div>
      SubComponent: { text }
    </div>
  );
}
const MemoizedSubComponent = React.memo(SubComponent);

SOURCE

Honey
  • 2,208
  • 11
  • 21
11

Memoization generates an additional cost corresponding to cache-related computations, this is why React re-renders components even when the props are referentially the same, unless you choose to memoize things using React.memo for instance.

If you consider for example a component that re-renders with different props very often, and if memoization was an internal implementation detail, then React would have to do 2 jobs on every re-rendering:

  • Check if the old and current props are referentially the same.
  • Because props comparison almost always returns false, React would then perform the diff of previous and current render results.

which means that you might end up with worse performance.

Hamza El Aoutar
  • 5,292
  • 2
  • 17
  • 23
  • Here's the documentation for [`React.PureComponent`](https://reactjs.org/docs/react-api.html#reactpurecomponent), [`React.memo`](https://reactjs.org/docs/react-api.html#reactmemo) and [a great article about `React.memo`](https://scotch.io/tutorials/react-166-reactmemo-for-functional-components-rendering-control) – benhatsor Jan 21 '21 at 07:59
  • I don't understand the reasoning here, React "does" diffing on every re-rendering for deciding whether to change DOM or not. So I don't think this is the case. My guess is that React does the re-rendering because it "needs to". Re-rendering in React is really nothing but re-running a function, but without running this function, React will not have a new render to compare with the previous one, so it's not performance optimization at all, it's an essential part of it. (I could be wrong though it's just my "guessing".) – aderchox Jun 18 '22 at 17:22
2

Wrapp your component with React.memo and it will not re-render

export const MyList: FC<Props> = React.memo(({
  initialList,
  //onItemsChanged,
}) => {
  console.log("render MyList");

  const [items, setItems] = useState(initialList);

  return (
    <div style={{ border: "5px solid blue" }}>
      <ul>
        {items.map((item) => (
          <li key={item.id}>{item.text}</li>
        ))}
      </ul>
      <button type="button">add list item (to be implemented)</button>
    </div>
  );
})

If you are looking at reason, please see this

ARZMI Imad
  • 950
  • 5
  • 8
  • Thanks, but my question is not about how to fix the re-rendering, but to understand why React does this (re-render children by default if the parent changes). – stefan.at.kotlin Jan 09 '21 at 14:51
  • It is a part of the answer, when you changed the state the parent should trigger re-render the view except the children protected by react memo – ARZMI Imad Jan 09 '21 at 14:54
  • But why is this done? Why re-render the children if the props didn't change? What issues could occur if React would do something like memo by default? – stefan.at.kotlin Jan 09 '21 at 15:37
  • You 're maybe looking at this https://stackoverflow.com/a/63405621/8537083 – ARZMI Imad Jan 09 '21 at 15:57
  • Thanks, that link is great. Having done some experiments, with memo the initial render time (which also includes useMemo, useCallback etc so props stay stable) increases slightly, but any further (prevented) rendering increases performance even on simple components. – stefan.at.kotlin Jan 09 '21 at 22:06
  • Welcome @stefan.at.wpf – ARZMI Imad Jan 18 '21 at 21:33
1

Here is a little analogy that should help

Let's say you have a box, and a box within that box. If you want to replace the outer box with a new box, then you must also replace the box inside it.

Your parent component is like the outer box, and the child component like the inner box. If you clear away the first render to make room for a new render, that new render will re-render new child components inside it.

I hope this helps clear things up for you.

Edit:

If you were not to re-render the child component inside the parent then any props that might be passed to the child component would not update within it and therefore, the child would be forever without dynamic props, and the whole purpose of ReactJS would be lost. You wouldn't be here to ask this question in the first place anyway if that was the case.

  • The analogy is only true when referring to unmounting the parent, but why should it rerender the child just because the parents props changed – Mordechai Jan 22 '21 at 20:27
  • The analogy is referring to re-rendering the parent. If props change in the parent, of course the parent will re-render, which means that all of it's child components must also re-render due to the UI being refreshed. – GoodStuffing123 Jan 22 '21 at 21:26
1

Well, the component only re-renders if shouldComponentUpdate() returns true, which it does by default. It is usually not much of a problem because the DOM will still not update if there are no changes. As an optimization, you can still add logic to keep your child components from re-rendering if its state and props have not changed, as follows:

shouldComponentUpdate(nextProps, nextState) {
    return (this.props.someProp !== nextProps.someProp && this.state.someState !== nextState.someState);
}

Do remember though this is just a performance optimization, when in doubt stick to the default behaviour. Here is a link to the official documentation.

Dharman
  • 30,962
  • 25
  • 85
  • 135
Deeksha Kaul
  • 264
  • 2
  • 7
1

When Parent Component render will not render child components,for using memo and useCallback.

Child Component:

Using memo will cause React to skip rendering a component if its props have not changed.

import { React, memo } from "react";
import { Typography, TextField } from "@mui/material";

function PasswordComponent(props) {
  console.log("PasswordComponenmt");
  return (
    <div>
      <Typography style={{ fontWeight: "900", fontSize: 16 }}>
        Password
      </Typography>
      <TextField
        size="small"
        type="password"
        variant="filled"
        value={props.password}
        onChange={(e) => {
          props.onChangePassword(e.target.value);
        }}
      />
    </div>
  );
}
export default memo(PasswordComponent);

Parent Component:

The React useCallback Hook returns a memoized callback function. The useCallback Hook only runs when one of its dependencies update. This can improve performance.

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

export default function ParentComponent() {
  const [password, setpassword] = useState("");

  const onChangePassword = useCallback((value) => {
    setpassword(value);
  }, []);

  return <PasswordComponent onChangePassword={onChangePassword} />;
}
ggorlen
  • 44,755
  • 7
  • 76
  • 106
soma iyappan
  • 482
  • 7
  • 16