Skip to content

Instantly share code, notes, and snippets.

@viksit
Forked from sebmarkbage/transferring-props.md
Created March 15, 2017 21:53
Show Gist options
  • Save viksit/a08ed3ac21502754961d13f63b17f172 to your computer and use it in GitHub Desktop.
Save viksit/a08ed3ac21502754961d13f63b17f172 to your computer and use it in GitHub Desktop.
Deprecating transferPropsTo

Deprecating transferPropsTo

It's a common pattern in React to wrap a component in an abstraction. The outer component exposes a simple property to do something that might have more complex implementation details.

We used to have a helper function called transferPropsTo. We no longer support this method. Instead you're expected to use a generic object helper to merge props.

render() {
  return Component(Object.assign({}, this.props, { more: 'values' }));
}

You can also use JSX spread attributes.

render() {
  return <Component {...this.props} more="values" />;
}

The rest of this article explains the best practices. It assumes that you're using our full JS transform pipeline but you can implement your own helpers if you don't want to use these language features.

Manual Transfer

Most of the time you should explicitly pass the properties down. That ensures that you only exposes a subset of the inner API, one that you know will work.

var FancyCheckbox = React.createClass({
  render: function() {
    var fancyClass = this.props.checked ? 'FancyChecked' : 'FancyUnchecked';
    return (
      <div className={fancyClass} onClick={this.props.onClick}>
        {this.props.children}
      </div>
    );
  }
});
React.renderComponent(
  <FancyCheckbox checked={true} onClick={console.log}>
    Hello world!
  </FancyCheckbox>,
  document.body
);

But what about the name prop? Or the title prop? Or onMouseOver?

Transferring with ... in JSX

Sometimes it's fragile and tedious to pass every property along. In that case you can use destructuring assignment with rest properties to extract a set of unknown properties.

List out all the properties that you would like to consume, followed by ...other.

var { checked, ...other } = this.props;

This ensures that you pass down all the props EXCEPT the ones you're consuming yourself.

var FancyCheckbox = React.createClass({
  render: function() {
    var { checked, ...other } = this.props;
    var fancyClass = checked ? 'FancyChecked' : 'FancyUnchecked';
    // `other` contains { onClick: console.log } but not the checked property
    return (
      <div {...other} className={fancyClass} />
    );
  }
});
React.renderComponent(
  <FancyCheckbox checked={true} onClick={console.log}>
    Hello world!
  </FancyCheckbox>,
  document.body
);

NOTE: In the example above, the checked prop is also a valid DOM attribute. If you didn't use destructuring in this way you might inadvertently pass it along.

Always use the destructuring pattern when transferring unknown other props.

var FancyCheckbox = React.createClass({
  render: function() {
    var fancyClass = this.props.checked ? 'FancyChecked' : 'FancyUnchecked';
    // ANTI-PATTERN: `checked` would be passed down to the inner component
    return (
      <div {...this.props} className={fancyClass} />
    );
  }
});

Merging Classes

The attributes listed to the right of the spread will override whatever was in other.className. However, it's often useful to also allow your consumers to add CSS classes to your components.

First add className to the list of destructured properties, and then use the joinClasses function to combine them.

var FancyCheckbox = React.createClass({
  render: function() {
    var { checked, className, ...other } = this.props;
    var fancyClass = checked ? 'FancyChecked' : 'FancyUnchecked';
    return (
      <div {...other} className={joinClasses(className, fancyClass)} />
    );
  }
});
React.renderComponent(
  <FancyCheckbox checked={true} className="lfloat" onClick={console.log}>
    Hello world!
  </FancyCheckbox>,
  document.body
);

NOTE: This is a fairly common pattern, yet it's still verbose. We're working on exposing a nicer syntax for joining classes.

Consuming and Transferring the Same Prop

If your component wants to consume a property but also pass it along, you can repass it explicitly checked={checked}. This is preferable to passing the full this.props object since it's easier to refactor and lint.

var FancyCheckbox = React.createClass({
  render: function() {
    var { checked, title, ...other } = this.props;
    var fancyClass = checked ? 'FancyChecked' : 'FancyUnchecked';
    var fancyTitle = checked ? 'X ' + title : 'O' + title;
    return (
      <label>
        <input {...other}
          checked={checked}
          className={fancyClass}
          type="checkbox"
        />
        {fancyTitle}
      </label>
    );
  }
});

NOTE: Order matters. By putting the {...other} before your JSX props you ensure that the consumer of your component can't override them. In the example above we have guaranteed that the input will be of type "checkbox".

Blacklisting Props

Don't try to preemptively blacklist properties that you're not using. It might be an anti-pattern to blacklist props that you're not using.

var Wrapper = React.createClass({
  render: function() {
    var {
      className, // unused variable
      children, // unused variable
      ...other
    } = this.props;
    return (
      <Inner {...other} />
    );
  }
});

If the Inner component isn't accepting the className or children props, then there's no problem allowing them. Nobody will pass them since they're a noop.

If the Inner component supports more features than you want expose, you can disable transferring by explicitly setting a value:

var Wrapper = React.createClass({
  render: function() {
    return (
      <Inner {...this.props} className={undefined} children={undefined} />
    );
  }
});

IMPORTANT: This might be an indication that you shouldn't use ... for transferring your props. Fallback to manual transfer if you need fine-grained control over which props you can transfer.

Rest and Spread Properties ...

Rest properties allow you to extract the remaining properties from an object into a new object. It excludes every other property listed in the destructuring pattern.

var { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x; // 1
y; // 2
z; // { a: 3, b: 4 }

Spread properties allow you to merge/concatenate properties from one object into a new one.

var n = { x, y, ...z };
n; // { x: 1, y: 2, a: 3, b: 4 }

These are experimental JavaScript features that we support in our JS transforms. Read more about the proposal

Read about JSX spread attributes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment