Skip to content

Instantly share code, notes, and snippets.

@andrewzey
Last active August 8, 2018 05:58
Show Gist options
  • Save andrewzey/9a836eaee90d4092c485fbb0668a46b7 to your computer and use it in GitHub Desktop.
Save andrewzey/9a836eaee90d4092c485fbb0668a46b7 to your computer and use it in GitHub Desktop.
Why you should either use createReactClass, or enable static properties in babel when working with React. Don't use plain ES6 classes!
import React, {Component} from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
// Problem: When writing React components, ES6 Classes present two annoyances:
// 1. No lexical pre-binding of `this` in class method references.
// 1a. Means we have to manually bind, but if we do that in render, we're
// creating lots of extraneous anonymous functions every render
// 1b. Alternately, we would have to meticulously "pre-bind" all of our
// class methods in the constructor (see example `MyES6Component`)
// 2. Awkwardness with lack of class static properties (esp. propTypes and defaultProps)
// Using CreateClass:
// *****************************************
// *****************************************
const MyReactComponent = createReactClass({
propTypes: {
doSomething: PropTypes.func,
},
// The fact that this is a function, rather than being `defaultProps` object like `propTypes` is a peculiar API choice
getDefaultProps() {
return {
doSomething: () => {},
};
},
handleClick(event) {
this.props.doSomething(event);
},
handleKeyUp(event) {
this.props.doSomething(event);
},
componentDidMount() {
// note that createReactClass lexically pre-binds this.handleKeyUp for us. Handy :)
window.addEventListener('keyup', this.handleKeyUp);
},
componentWillUnmount() {
// note that React lexically pre-binds this.handleKeyUp for us, and this is an
// identical function reference as when we registered the listener. Handy :)
window.removeEventListener('keyup', this.handleKeyUp);
},
render() {
// note that React lexically pre-binds this.handleKeyUp for us. Handy :)
return <div onClick={this.handleClick}></div>;
},
});
// Using ES6 Class
// *****************************************
// *****************************************
class MyES6Component extends Component {
constructor(props) {
// Boilerplate
super(props);
// Yuck, we have to do this for all class methods?!
// I suppose we could make a new base class or HOC that does this, but then
// we have lots of API surface area to support / reimplement. Gross.
this.handleClick = this.handleClick.bind(this);
this.handleKeyUp = this.handleKeyUp.bind(this);
}
handleClick(event) {
this.props.doSomething(event);
}
handleKeyUp(event) {
this.props.doSomething(event);
}
componentDidMount() {
// Note, we could NOT have done the binding here, as then addEventListener
// and removeEventListener would have different function references (each
// their own anonymous function)
window.addEventListener('keyup', this.handleKeyUp);
}
componentWillUnmount() {
// Note, we could NOT have done the binding here, as then addEventListener
// and removeEventListener would have different function references (each
// their own anonymous function)
window.removeEventListener('keyup', this.handleKeyUp);
}
render() {
// Note that we could have defined the arrow function at call time like this:
// return <div onClick={event => this.handleClick(event)}></div>;
// but that means we're creating a new anonymous function on every render!!
// so instead we use the prebound version just like in createClass
return <div onClick={this.handleClick}></div>;
}
}
// eww, why is this out here?!
// ES6 should have included class properties (statics)!
MyES6Component.propTypes = {
doSomething: PropTypes.func,
};
// eww, why is this out here?!
// ES6 should have included class properties (statics)!
MyES6Component.defaultProps = {
doSomething: () => {},
};
// Using ES6 Class with ES7 Class Properties (Static and Instance)
// *****************************************
// *****************************************
class MyES7Component extends Component {
// no longer floating outside the class definition. hooray!
static propTypes = {
doSomething: PropTypes.func,
}
// no longer floating outside the class definition. hooray!
static defaultProps = {
doSomething: () => {},
}
// instance property that's bound. hooray!
handleClick = event => {
this.props.doSomething(event);
}
// instance property that's bound. hooray!
handleKeyUp = event => {
this.props.doSomething(event);
}
componentDidMount() {
window.addEventListener('keyup', this.handleKeyUp);
}
componentWillUnmount() {
window.removeEventListener('keyup', this.handleKeyUp);
}
render() {
return <div onClick={this.handleClick}></div>;
}
}
@andrewzey
Copy link
Author

@andrewzey
Copy link
Author

On the flip side, class properties are still only Stage 2 (Draft).

So the "safe" thing to do is probably to just stick with createReactClass...

@andrewzey
Copy link
Author

andrewzey commented May 12, 2017

The ES7 version is likely future proof, as I doubt the syntax and semantics there will change, but it's possible, and that could make a horrible debugging experience should anything be wrong with Babel's implementation.

Basically it boils down to whether we want to refactor now, making our code more "standard JS", but running the risk of there being implementation issues with the Stage 2 proposal - OR - wait it out and continue to use createReactClass until the proposal is more mature and we can be sure we aren't jumping the gun in making debugging more painful.

@andrewzey
Copy link
Author

Our eventual goal is for ES6 classes to replace React.createClass completely, but until we have a replacement for current mixin use cases and support for class property initializers in the language, we don't plan to deprecate React.createClass.

via: https://facebook.github.io/react/blog/2015/03/10/react-v0.13.html

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