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.
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
?
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} />
);
}
});
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.
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"
.
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 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