React hooks was recently announced at #reactconf, slated for v16.7.x. See documentation and RFC here. Hooks added the ability to attach state and effects to previously stateless function components. While the change is backward compatible, the implication to how we use React is profound.

Personally I was stoked to see this introduction, because it solved a long running problem I had while building components.

I try to use stateless component whenever possible – it's light weight, easy to test, and has minimum side effects. Most components, however, would inadvertently need one or two states, or attach to a lifecycle event for better performance. This always result in converting a simple stateless component to a full blown class-based one.

Class Bloat

const MyStatelessComponent = props => React.createElement('div', null, props.name)

Here is an example of a stateless component.

var MyStatelessComponent = function MyStatelessComponent(props) {
  return React.createElement(
    "div",
    null,
    props.name
  );
}

Run it through ES5 transpiler and here is the result.

class MyComponentClass extends React.Component {
  render() {
    return <div>{this.props.name}</div>
  }
}

A functionally equivalent component in class.

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var MyComponentClass = function (_React$Component) {
  _inherits(MyComponentClass, _React$Component);
function MyComponentClass() {
    _classCallCheck(this, MyComponentClass);
return _possibleConstructorReturn(this, (MyComponentClass.__proto__ || Object.getPrototypeOf(MyComponentClass)).apply(this, arguments));
  }
_createClass(MyComponentClass, [{
    key: "render",
    value: function render() {
      return React.createElement(
        "div",
        null,
        this.props.name
      );
    }
  }]);
return MyComponentClass;
}(React.Component);

Although the ES5 transpiler is not an accurate measurement of how fast the code will be parsed and run, it demonstrates the added computational complexity when using classes.

Next Step

Here are some follow up posts where I document some of the common hurtles while migrating to hooks.