110

I haven't been able to find a clear answer to this, hope this isn't repetitive.

I am using React + Redux for a simple chat app. The app is comprised of an InputBar, MessageList, and Container component. The Container (as you might imagine) wraps the other two components and is connected to the store. The state of my messages, as well as current message (the message the user is currently typing) is held in the Redux store. Simplified structure:

class ContainerComponent extends Component {
  ...
  render() {
    return (
      <div id="message-container">
        <MessageList 
          messages={this.props.messages}
        />
        <InputBar 
          currentMessage={this.props.currentMessage}
          updateMessage={this.props.updateMessage}
          onSubmit={this.props.addMessage}
        />
      </div>
    );
  }
}

The issue I'm having occurs when updating the current message. Updating the current message triggers an action that updates the store, which updates the props passing through container and back to the InputBar component.

This works, however a side effect is that my MessageList component is getting re-rendered every time this happens. MessageList does not receive the current message and doesn't have any reason to update. This is a big issue because once the MessageList becomes big, the app becomes noticeably slower every time current message updates.

I've tried setting and updating the current message state directly within the InputBar component (so completely ignoring the Redux architecture) and that "fixes" the problem, however I would like to stick with Redux design pattern if possible.

My questions are:

  • If a parent component is updated, does React always update all the direct children within that component?

  • What is the right approach here?

Nicholas Haley
  • 3,558
  • 3
  • 29
  • 50
  • 2
    even if render() is run, it should be fast - it won't update the DOM at all unless there are actual changes. so re-rendering children looks like the safest behavior to me. However I dont actually know if react is supposed to do this, so this is not an answer – William B Nov 26 '16 at 15:40

4 Answers4

132

If a parent component is updated, does React always update all the direct children within that component?

No. React will only re-render a component if shouldComponentUpdate() returns true. By default, that method always returns true to avoid any subtle bugs for newcomers (and as William B pointed out, the DOM won't actually update unless something changed, lowering the impact).

To prevent your sub-component from re-rendering unnecessarily, you need to implement the shouldComponentUpdate method in such a way that it only returns true when the data has actually changed. If this.props.messages is always the same array, it could be as simple as this:

shouldComponentUpdate(nextProps) {
    return (this.props.messages !== nextProps.messages);
}

You may also want to do some sort of deep comparison or comparison of the message IDs or something, it depends on your requirements.

EDIT: After a few years many people are using functional components. If that's the case for you then you'll want to check out React.memo. By default functional components will re-render every time just like the default behavior of class components. To modify that behavior you can use React.memo() and optionally provide an areEqual() function.

GJK
  • 37,023
  • 8
  • 55
  • 74
  • While this works, I find that it doesn't update on componentDidMount. Any idea what the work around for this is? – Nicholas Haley Nov 26 '16 at 17:01
  • 2
    I'm not sure what you mean. What doesn't update on `componentDidMount`? – GJK Nov 26 '16 at 17:07
  • To clarify, the initial messages are no being loaded when the component is mounted. It is only after a message is added (satisfying the condition `(this.props.messages !== nextProps.messages)`), that all the messages are displayed – Nicholas Haley Nov 27 '16 at 16:52
  • Never mind I found the issue, it's a side effect of some other prop changes not triggering a re-render – Nicholas Haley Nov 27 '16 at 17:04
  • Glad you got it figured out. Just for reference, [here's](https://facebook.github.io/react/docs/react-component.html#the-component-lifecycle) the React component lifecycle that explains when the different hooks are called. – GJK Nov 27 '16 at 17:05
  • 4
    So if you don't implement `shouldComponentUpdate`, will a parent always rerender all of it's children? – Galupuf Feb 01 '19 at 00:07
  • 2
    If you're inheriting directly from `React.Component` (and not `React.PureComponent` or something similar), then yes, it will always re-render. – GJK Feb 01 '19 at 22:19
  • Should the above `shouldComponentUpdate` be implemented in `ContainerComponent ` or `MessageList ` to solve unnecessary rerender of `MessageList`? – Johnson Jun 18 '20 at 02:19
  • In `MessageList`. Compare `nextProps` to `this.props` and decide to re-render accordingly. – GJK Jun 18 '20 at 14:06
  • Why does React rerender all child components when parent rerenders even if the child component's props or state hasn't changed? – darKnight Mar 02 '23 at 19:41
58

If a parent component is updated, does React always update all the direct children within that component? -> Yes , by default if parent changes all its direct children are re-rendered but that re-render doesn't necessarily changes the actual DOM , thats how React works , only visible changes are updated to real DOM.

What is the right approach here? -> To prevent even re-rendering of virtual DOM so to boost your performance further you can follow any of the following techniques:

  1. Apply ShouldComponentUpdate Lifecycle method - This is applied only if your child component is class based , you need to check the current props value with the prev props value ,and if they are true simply return false.

  2. Use Pure Component -> This is just a shorter version to above method , again works with class based components

  3. Use React memo -> this is the best way to prevent Rerendering even if you have functional components ,you simply need to wrap your components export with React.memo like : export default React.memo(MessageList)

EDIT: Please note: If you are passing a function or any object as a prop to your child component then even if you use React.memo the child component will still be re-rendered. This is because functions are objects in Javascript and everytime parent re-renders a new function object is created so React treats it as a different prop. example :

const onClickHandler = () => {
     console.log('button is clicked')
    }

return (
  <Button onClick={onClickHandler}>
)

In the above example Button component will always be re-rendered if Parent is rendered, to solve this above problem we can use another React hook : useCallback - This hooks allows to maintain the same object or function between re-renders.

const onClickHandler = useCallback(() => {
     console.log('button is clicked')
    },[])

return (
  <Button onClick={onClickHandler}>
)

Now if you use React.memo in Button component like : export default React.memo(Button) it will not re-render unless the props are actually changed.

Hope that helps!

lokeshj
  • 888
  • 6
  • 11
  • 2
    Thanks. I've been trying to use usememo, which didn't work. All I had to do was just wrapping component with React.memo() as you said. – Jaeseok An Jun 26 '20 at 09:05
  • One more simple way: If we destructure the child component from the parent and pass it as children prop the child component won't re-render. Example: `const Parent = ({children}) =>
    {children}
    , const App = () => `
    – Patrik Rikama-Hinnenberg Feb 05 '22 at 12:25
  • @lokeshj, in the beginning of your answer you mention _Yes , by default if parent changes all its direct children are re-rendered_, but then in your last example with `useCallback` you say _Now if you use React.memo in Button component it will not re-render_, these are contradicting statements. Say a parent doesn't pass any props to the child `button`, now if parent updates, would it cause button to re-render? – user31782 Jun 22 '23 at 11:56
  • @user31782 I didn't get you, how are these contradicting statements, I have mentioned By-Default all direct children will be rerendered, and React.memo is one of the way if we want to prevent re-rendering of children components in some specific case. Hope you got the point. Also Child component will be re-rendered even if you don't pass ant props, assume it like a function call with or without parameters – lokeshj Jun 24 '23 at 14:59
  • @lokeshj I didn't know that even components with no props or state get re rendered as well if their parent re renders. This way you are correct, _by default_ parent re render will trigger children re render. I [tested with memo](https://codepen.io/user31782/pen/YzRpRpG?editors=0011) as well and it doesn't re-render, I assume react does a `Object.is` comparison of virtual dom on re render and a parent's new render sees `` as a new object every time. – user31782 Jun 24 '23 at 19:14
10

If parent component props have changed it will re-render all of its children which are made using React.Component statement.

Try making your <MessageList> component a React.PureComponent to evade this.

According to React docs: In the future React may treat shouldComponentUpdate() as a hint rather than a strict directive, and returning false may still result in a re-rendering of the component. check this link for more info

Hope this helps anyone who is looking for the right way to fix this.

sidd
  • 629
  • 10
  • 30
2

If you're using map to render child components and using a unique key on them (something like uuid()), maybe switch back to using the i from the map as key. It might solve the re-rendering issue.

Not sure about this approach, but sometimes it fixes the issue

Prottay Rudra
  • 187
  • 2
  • 9
  • 1
    Please be very careful with this suggestion. To quote the [official docs](https://reactjs.org/docs/lists-and-keys.html#keys): 'When you don’t have stable IDs for rendered items, you may use the item index as a key as a __last resort__. We don’t recommend using indexes for keys if the order of items may change. This can negatively impact performance and may cause issues with component state.' Also check out this [in-depth explanation](https://robinpokorny.com/blog/index-as-a-key-is-an-anti-pattern/). – iron9 Aug 24 '22 at 07:55