4

In my React app, I am using a few libraries (i.e. Material UI and React Intl) to pass React elements as props from higher-level components down to "dumb components" that have one job: to render.

Smart component:

import ActionExplore from 'material-ui/svg-icons/action/explore';
import { FormattedMessage } from 'react-intl';

export class SmartComponent extends Component {
  render() {
    return (
      <div>
        <DumbComponent
          text={<FormattedMessage id="en.dumbComponent.text" />}
          icon={<ActionExplore/>}
        />
        <AnotherDumbComponent {props.that.are.changing} />
      </div>
    );
  }
}

Dumb component:

import shallowCompare from 'react-addons-shallow-compare';

export class DumbComponent extends Component {
  shouldComponentUpdate(nextProps, nextState) {
    return shallowCompare(this, nextProps, nextState);
  }

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

The benefit of doing this, is that the DumbComponent does not need to know anything about application logic (material ui, internationalization, etc.). It simply renders, leaving SmartComponent to take care of all the business logic.

The downside I am encountering with this approach is performance: DumbComponent will always re-render, even when AnotherDumbComponent's props change instead of its own, because shouldComponentUpdate always returns true. shouldComponentUpdate is unable to do an accurate equality check between React elements in the above example.

How can React elements be equality-checked in shouldComponentUpdate? Is this too costly to perform? Is passing React elements as props to dumb components a bad idea? Is it possible to not pass down React elements as props, yet keep components dumb? Thanks!

Jon Cursi
  • 3,301
  • 4
  • 27
  • 53
  • You just have to make sure it `shouldComponentUpdate` returns `false` if you don't want to re-render. If it's simple enough, why not create your own logic? Such as: `if (this.props.text !== nextProps.text || this.props.icon !== nextProps.icon) return true; return false;`. – Yuya Jun 06 '16 at 20:07
  • Could you add an example of how your mutating your props ? Because I you don't ensure immutability equality check will always return true, even for "same" data. – Pierre Criulanscy Jun 06 '16 at 21:29

2 Answers2

0

Whether it is performant or not is going to depend on your use case. As a rule of thumb, I think this kind of logic is best used when you expect user input to only impact children of the "dumb component", rather than it's peers.

As an example, Material-UI's Dialog has almost identical structure to what you suggest for it's action buttons and title. (https://github.com/callemall/material-ui/blob/master/src/Dialog/Dialog.js#L292). But it works well in that case because those elements are on the modal, which itself doesn't modify unless you are opening or closing it.

As another potential workaround, what if you passed in the objects needed to create the children elements, without passing in the entire element? I haven't tried this, so I'm not sure how well it will work, but it might be worth a try. Something to the extent of;

<DumbComponent 
    textComponent={FormattedMessage} 
    textProps={{id: "en.dumbComponent.text"}}/>

//In DumbComponent;

render(){
    return (
        <div>
            <h1>
                <this.props.textComponent {...this.props.textProps}/>
            </h1>

        </div>
    );
}

Might give you some more concrete things to play around with when determining if you should update or not.

Jake Haller-Roby
  • 6,335
  • 1
  • 18
  • 31
-1

I solved this by converting these plain React elements into Immutable.js maps, and passing those as props instead.

This article was very helpful: https://www.toptal.com/react/react-redux-and-immutablejs

Immutable.js allows us to detect changes in JavaScript objects/arrays without resorting to the inefficiencies of deep equality checks, which in turn allows React to avoid expensive re-render operations when they are not required.

Jon Cursi
  • 3,301
  • 4
  • 27
  • 53