Re-Render React Component When Its Props Change

One of the core features of React is its efficient updating of the DOM when a component‘s props or state changes. By intelligently re-rendering only the components that have actually changed, React provides a major performance boost over manual DOM manipulation or blunt force re-rendering of an entire app. However, there are some subtleties and "gotchas" when it comes to getting your components to update properly when their props change. In this article, we‘ll take an in-depth look at what causes a React component to re-render and some best practices to ensure your components are updating as expected.

React Re-Rendering Basics

First, let‘s review the fundamentals of when and why a React component re-renders. A re-render can be triggered by:

  1. A change in the component‘s state
  2. New props being passed to the component
  3. A parent component re-rendering

When a component‘s state changes via the setState method, React automatically re-renders the component to reflect the new state. Similarly, when new props are passed to a component, React re-renders it with the new prop values. Lastly, if a parent component re-renders, all of its child components re-render as well regardless of whether their props have changed.

It‘s important to note that by default, React does a shallow comparison of a component‘s previous and next props and state to determine if a re-render is necessary. For primitive values like strings and numbers, a re-render only occurs if the values have changed. But for arrays, objects, and functions, a re-render occurs even if the contents are the same because the references have changed.

Here‘s a simple example showing how a state change triggers a re-render:

import React, { Component } from ‘react‘;

class Example extends Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

Each time the button is clicked, the component‘s state is updated with a new count value. React detects this state change and re-renders the component, updating the displayed count.

Child Component Not Updating with New Props

A common issue is a child component not updating when it receives new props from its parent. This can happen for a few reasons:

  1. The new prop values are the same as the old values, so React doesn‘t trigger a re-render.
  2. The child component is extending PureComponent instead of Component, so React is doing a shallow prop comparison and not detecting a change.
  3. The child component‘s shouldComponentUpdate lifecycle method is preventing a re-render.

Here‘s an example demonstrating the first scenario:

class Parent extends Component {
  state = {
    items: [1, 2, 3]
  };

  render() {
    return (
      <Child items={this.state.items} />
    );
  }
}

class Child extends Component {
  render() {
    return (
      <ul>
        {this.props.items.map(item => <li key={item}>{item}</li>)}  
      </ul>
    );
  }
}

If the Parent component updates its state.items array to be a new array with the same values ([1, 2, 3]), the Child won‘t re-render because the contents of the items prop haven‘t changed, only the reference.

To force a re-render in this case, you could either:

  1. Change the Child to extend PureComponent so it does a shallow prop comparison
  2. Give each item in the array a unique key prop so React detects the new references
  3. Change the Parent to pass the items as individual props instead of an array
<Child item1={this.state.items[0]} item2={this.state.items[1]} item3={this.state.items[2]} />

Using componentDidUpdate for Prop Changes

If you need to perform some action in a child component whenever its props change, you can use the componentDidUpdate lifecycle method. This method is called after every render, and you can compare the component‘s current props to its previous props to determine what, if anything, has changed.

class Child extends Component {
  componentDidUpdate(prevProps) {
    // Typical usage, don‘t forget to compare the props
    if (this.props.items !== prevProps.items) {
      console.log(‘items prop changed!‘);
      // do something
    }
  }

  render() {
    // render using this.props
  }
}

Just be sure to always compare the current and previous prop values, otherwise you‘ll end up in an infinite loop since componentDidUpdate is called after every render.

Optimizing Re-Renders with shouldComponentUpdate

In some cases, you may want to prevent a component from re-rendering even if its props have changed. You can do this by implementing the shouldComponentUpdate lifecycle method, which lets you return false if you know a re-render is not needed.

class Child extends Component {
  shouldComponentUpdate(nextProps, nextState) {
    // Only re-render if the ‘text‘ prop has changed
    if (this.props.text !== nextProps.text) {
      return true;
    }
    return false;
  }

  render() {
    return <div>{this.props.text}</div>;
  }
}

React also provides a PureComponent base class that implements shouldComponentUpdate with a shallow prop and state comparison. If your component only has simple props and state and its render output only depends on those props and state, extending PureComponent can give you a performance boost by avoiding unnecessary re-renders.

Redux and Component Updates

When using a state management library like Redux, you‘ll often connect your components to the Redux store using the connect higher-order component. By default, a connected component will re-render whenever the root state object of your Redux store changes.

To avoid unnecessary re-renders, you can use selectors to pull out only the specific pieces of state that a component needs. The connect function will do a reference equality check on the results returned from the selectors, and only trigger a re-render if those references have changed.

// With selectors
const mapStateToProps = state => ({
  items: getItemsForComponent(state)
});

export default connect(mapStateToProps)(MyComponent);

// Without selectors 
const mapStateToProps = state => ({
  items: state.items
});

export default connect(mapStateToProps)(MyComponent);

In the first "with selectors" example, MyComponent will only re-render when the result of getItemsForComponent(state) changes. But in the second example without selectors, MyComponent will re-render whenever any part of the root state changes, even if this.props.items hasn‘t actually changed.

Best Practices for Reliable Component Updates

To ensure your components are updating reliably when their props or state change, follow these best practices:

  1. Always use keys when rendering arrays of elements to help React identify which items have changed.
  2. Be careful when mutating objects or arrays in a component‘s state. Use concat, slice, or the spread operator to create new copies instead.
  3. Use PureComponent or shouldComponentUpdate to avoid unnecessary re-renders of child components.
  4. With Redux, use selectors to extract and return only the state data a component needs.
  5. If a child component needs to update the parent‘s state, pass down an "update" function as a prop instead of trying to modify the parent‘s state directly.
// parent component
updateItem = (itemId, itemContent) => {
  const updatedItems = this.state.items.map(item => {
    if (item.id === itemId) {
      return { ...item, content: itemContent };
    }
    return item;
  });

  this.setState({ items: updatedItems });
}

render() {
  return (
    <ChildComponent 
      item={this.state.items[0]}
      updateItem={this.updateItem}
    />
  );  
}

// child component
handleChange = (event) => {
  this.props.updateItem(this.props.item.id, event.target.value);
}

By following these guidelines and understanding how React handles updates, you can ensure your components are rendering efficiently and updating properly as their props and state change. The key is to be mindful of when and why re-renders happen and use the appropriate tools and techniques to optimize your component structure. With practice and experience, keeping your React components in sync with their data will become second nature.

Similar Posts