angular_learner
angular_learner

Reputation: 671

Redux mapStateToProps called multiple times

I have this very simple Component, which is connected to redux state and returns {fruit, vegetables}. Everything works fine, but let's say I have a graph inside the Component and I if receive only updated vegetable from API the graph is being recreated each time.

Here's my component:

const Products = ({ fruit, vegetable }) =>
  <div className='Products'>
      <div>{vegetable.map(....logic here)}</div>
      <div>{Math.random()}</div> // this is to illustrate the component is rendering every time
      <Graph>Here will be a chart or a graph with fruit</Graph> //but it gets re-rendered even though there is no new fruit
  </div>

const mapStateToProps = (state) => {
  return {
    fruit: state.data.fruit,
    vegetable:  state.data.vegetable,
  }
}

export default connect(mapStateToProps)(Products)

It seems to me that every-time, no matter which states is updated it re-renders the whole components.

Is there a way to prevent that?

Upvotes: 6

Views: 7263

Answers (3)

dattebayo
dattebayo

Reputation: 1402

Given your current code. React will update the whole component when state is changed. So Graph Component will get updated.

If you don't want Graph Component to get updated you can add shouldComponentUpdate in your Graph Component and introduce checks there for re-rendering like as follows

shouldComponentUpdate: function(nextProps, nextState) {
    // You can access `this.props` and `this.state` here
    // and check them against nextProps and nextState respectively.
    // return boolean(false) if you don't want the component to re-render.
  }

Upvotes: 2

VonD
VonD

Reputation: 5155

When a React component gets rendered, the whole tree of components below it also gets rendered - at the exception of the components which shouldComponentUpdate hook returns false. So in your case, if the Products component gets rendered, it is normal that the Graph component also does.

You have two options here:

  • if your Products component does not use the fruit prop outside of the Graph component, you can connect directly your Graph component to the fruitstate, and use the pure option of the connect function to avoid re-renders when fruit does not change

  • you can define the shouldComponentUpdate hook in your Graph component to manually skip unnecessary renders, or use a helper library to do it for you, for example the pure helper of the recompose library

The first option is where optimizing react/redux apps / avoiding unnecessary renders generally starts: connect your components to the store at the lowest level where it makes sense. The second option is more of an escape hatch - but still often useful.

As you mention you use stateless components, you can use a higher-order component to benefit from the shouldComponentUpdate hook. To understand how this works, here's how a simple implementation of it could look like this:

function pure(BaseComponent, shouldUpdateFn) {
    return class extends Component {
        shouldComponentUpdate(nextProps) {
            return shouldUpdateFn(this.props, nextProps);
        }
        render() {
            return <BaseComponent { ...this.props } />;
        }
    }
}

This would give you a pure HOC that you could reuse over your app to avoid unnecessary renders: it works by wrapping your stateless component into a new component with the desired hook. You'd use it like so, for example:

export default pure(Graph, (props, nextProps) => props.fruit !== nextProps.fruit)

Still, i highly encourage you in having a look at recompose, which has more fine-grained implementations of this, and would avoid you to reinvent the wheel.

Upvotes: 7

Damien Leroux
Damien Leroux

Reputation: 11693

To prevent a component to rerender when receiving new props, you can implement shouldcomponentupdate() in Graph.

Use shouldComponentUpdate() to let React know if a component's output is not affected by the current change in state or props. The default behavior is to re-render on every state change, and in the vast majority of cases you should rely on the default behavior.

shouldComponentUpdate() is invoked before rendering when new props or state are being received. Defaults to true. This method is not called for the initial render or when forceUpdate() is used.

Returning false does not prevent child components from re-rendering when their state changes.

Currently, if shouldComponentUpdate() returns false, then componentWillUpdate(), render(), and componentDidUpdate() will not be invoked. Note that 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.

If you determine a specific component is slow after profiling, you may change it to inherit from React.PureComponent which implements shouldComponentUpdate() with a shallow prop and state comparison. If you are confident you want to write it by hand, you may compare this.props with nextProps and this.state with nextState and return false to tell React the update can be skipped.

To help you implementing shouldComponentUpdate(), you can use eitherReact shallow-compare() or a custom shallow compare function

Upvotes: 2

Related Questions