Skip to content

Instantly share code, notes, and snippets.

@sebmarkbage
Last active August 2, 2022 10:44
  • Star 57 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save sebmarkbage/a6e220b7097eb3c79ab7 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.

@EvHaus
Copy link

EvHaus commented Nov 13, 2014

It's important to note that the JSX --harmony mode must be enabled if you want to have any hope of having destructuring assignments work in current browser versions.

@petebrowne
Copy link

Are you deprecating ReactPropTransfer altogether? Would this be a valid alternative to this.transferPropsTo for merging class names?

var mergeProps = require('react/lib/ReactPropTransferer').mergeProps;

var FancyCheckbox = React.createClass({
  render: function() {
    var fancyClass = this.props.checked ? 'FancyChecked' : 'FancyUnchecked';
    var transferProps = mergeProps({className: fancyClass}, this.props);
    return (
      <div {...transferProps} />
    );
  }
});

@simkimsia
Copy link

@globexdesigns Newbie here. I have googled but I cannot find how to turn on harmony mode. Can advise?

@cuongcua90
Copy link

@simkimsia do you use webpack or browserify for your project?

@simkimsia
Copy link

@cuongcua90 I use cdn.

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.0-beta.1/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.0-beta.1/JSXTransformer.js"></script>

So I use

<body>
    <script type="text/jsx;harmony=true">
    /** @jsx React.DOM */
    var APP = React.createClass({
        render: function() {
            return (
                <div>

Correct?

@simkimsia
Copy link

For the merging classes, I get a joinClasses function not defined error. Is that a native reactjs function?

@khaled
Copy link

khaled commented Apr 6, 2015

You need to require joinClasses:
var joinClasses = require( 'react/lib/joinClasses' );

@wmertens
Copy link

wmertens commented May 6, 2016

@sebmarkbage why doesn't JSX allow <Component {prop1: 7, prop3, ...other, foo: "bar"}/> but does allow <Component {...other}/>?

The latter is equivalent to React.createElement(Component, {...other}) so I would expect the former to be equivalent to React.createElement(Component, {prop1: 7, prop3, ...other, foo: "bar"}). Instead it's just invalid…

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