Just want the component? Find it at GitHub
Possibly the most frequently re-implemented code across any React component is that used to pass properties through to child components. This stems from the fact that you generally need some sort of input to make the component useful, while you don’t want these component-specific properties polluting the props
on your children.
This would be all well and good if it wasn’t for the fact it is so easy to avoid re-implementing this over and over again! I mean, you’re already defining the properties you consume in propTypes
– why repeat yourself?
In fact, by passing your React classes through a Higher-Order Component, you can easily add a method which returns all props except the ones specified in propTypes
– making writing components that-much-easier (and consistent). I’ll show you how to do it in a moment, but first lets have a look at:
The Old Way Of Doing Things
Say we wanted to write a NavBar
component for our app, which is possible to display in two flavours: 'dark'
and 'light'
. To do this, we’ll accept a theme
property and apply different styles to the underlying <div>
by assigning a theme-specific className
.
To make our NavBar useful in a variety of contexts, we want to make sure that properties such as style
and onClick
are passed into the generated <div>
. But given that we don’t want our theme
property to appear in the DOM, we can’t just write <div {...this.props} />
and pass through everything.
How do we accomplish this? Well, the immediately obvious way of doing this is to go through and manually transfer each of the props we want to pass through, rendering something along the lines of this:
const classes = `NavBar NavBar-${this.props.theme} ${this.props.className}`;
return <div style={this.props.style} onClick={this.props.onClick} className={classes}>{this.props.children}</div>;
But this breaks down when we want to use the component in ways we didn’t forsee – like if we wanted to add an onKeyDown
handler, for example.
Our component really shouldn’t have to worry about how it’s consumer wants to use the underlying <div>
, and as such, it makes sense to pass through all of the received props
except those we specifically want to use. To do this, we might follow React’s transferring props documentation and try using ES7’s experimental object rest properties feature:
const { this.props.theme, className, children, ...other } = this.props;
const classes = `NavBar NavBar-${theme} ${className}`;
return <div {...other} className={classes}>{children}</div>;
This isn’t a bad way of doing things – but it could be done better. That’s where propTypes
comes in.
Don’t Repeat Yourself
The propTypes
class property of your React components is how you tell React about the various properties you expect to receive. For our NavBar
class, it might look something like this:
NavBar.propTypes = {
theme: React.PropTypes.oneOf(['dark', 'light']),
className: React.PropTypes.string,
};
During development, React uses this to alert you when your components aren’t behaving as expected. But that doesn’t prevent us from using it in other ways!
In particular, we can use it to get a list of properties we don’t want to pass through to our child components:
const omit = Object.keys(NavBar.propTypes)
And then using the except package on npm, we can easily extract these keys from this.props
:
const other = except(this.props, omit)
Putting this together, we could write our NavBar
component’s render
function like so:
const classes = `NavBar NavBar-${this.props.theme} ${this.props.className}`;
const other = except(this.props, Object.keys(NavBar.propTypes))
return <div {...other} className={classes}>{this.props.children}</div>;
Great, we’re not repeating ourself anymore! However, we still have the small problem of the snippet being our most complicated and unwieldy one so far.
Once upon a time, we may have tried to mitigate this by generating our other
object from a method in a React mixin – but with ES6 classes, we can go one better.
Higher Order Components
While ES6 classes may look special on the surface, under the hood they’re just sugar for vanilla functions with a bunch of prototype methods. And just like vanilla functions, we can pass them around as arguments and define new properties on their prototype after the fact.
This allows us to modify or wrap our ES6 classes programatically. People call the functions that do this “Higher-Order Components”.
Let’s built a higher order component which adds a passthrough
method to the prototype of whatever function we pass in. Actually, maybe let’s get you to write it for practice. It’ll look something like this:
function addPassthroughMethod(component) {
// Add your `passthrough` method to component here
}
Once you’ve had a shot, check our answer against mine by touching or hovering your mouse over the box below:
import except from 'except'
export default function addPassthroughMethod(component) {
// TODO: define this as a getter instead of as a fn
component.prototype.other = function passthrough() {
const omit = Object.keys(component.propTypes || {})).concat('children')
return except(this.props, omit)
}
}
There are some properties, like
children
, which you never want to automatically pass through – you can add these in your passthrough
function so you don’t need to add them to propTypes every time.Great! Now you can add a passthrough
method to any React component just by running addPassthroughMethod
on it. Using the new method is as simple as ensuring your propTypes
are up to date, and then passing the result of passthrough()
into one of the components in your render
function:
class MyComponent extends React.Component {
render() {
return <div {...this.passthrough()}>{this.props.children}</div>
}
}
MyComponent.propTypes = {
...
}
addPassthroughMethod(MyComponent)
It couldn’t get any simpler. Or could it?
Improving readability with ES7 decorators and class properties
The great thing about Higher-Order Components is they can be used as ES7 class decorators! Combined with ES7’s class properties proposal, you can accomplish the whole thing in an pleasingly simple manner:
@addPassthroughMethod
class Paper extends React.Component {
static propTypes = {
...
}
render() {
return <div {...this.passthrough()}>{this.props.children}</div>
}
}
See the ES7 decorators and class properties proposals for more details.
Some people may argue against using Decorators/Higher-Order Components to modify the passed in component, and suggest that it would make be more elegant to extend the passed-in class with a
render
method which passes through the passthrough
props to the existing render
method as parameters.
While this may look more “functional”, the reality either way is that the decorated class needs to know that it will be decorated. Given that it is easy to compose multiple decorators which modify the prototype, I’d say this is the more pragmatic option.
Theres an NPM module for that
Now you know how to write your own passthrough decorator, and they say knowing is half the battle! But is there any point finishing the battle off when you can just npm install
something which does all this (and more)?
npm install react-passthrough
Just like the above example, react-passthrough adds a passthrough()
method to your React components. However, unlike our addPassthroughMethod
function above, react-passthrough let’s you specify which properties you’d like to always omit
(defaulting to ['children']
), as well as which properties you’d always like to force
inclusion of. Here is an example of usage:
import passthrough from 'react-passthrough'
@passthrough({force: ['disabled', 'tabindex'], omit: ['children', 'form']})
class Control extends React.Component {
render() {
return <div {...this.passthrough()}>{this.props.children}</div>
}
}
react-passthrough was extracted from my Memamug (my open-source React app) – and it isn’t alone! If you’ve found this useful, you’ll may also find some utility from my other components and articles. Sign up for my mailing list to learn about them! In return for your e-mail, you’ll also immediately receive 3 bonus print-optimised PDF cheatsheets – on React, ES6 and JavaScript promises.
Great post. Thank you. Best explanation and usage of es7 decorators I have ever seen. You really nailed it down with simple to complex approach.
Thank you again.
Now that PropTypes checkers will be stripped out of React in production (https://facebook.github.io/react/warnings/dont-call-proptypes.html) and people are even very keen on removing PropTypes declarations too. (https://github.com/oliviertassinari/babel-plugin-transform-react-remove-prop-types), is this still safe to use in production?
It appears to me that the mainstream’s opinion is that PropTypes is just for development. For that reason, in long term, it should be replaced with something like typescript or flow. What are your view of PropTypes as a runtime contract?