How to Use Classes and Sleep at Night

There is a growing sentiment in the JavaScript community that ES6 classes are not awesome:

  • Classes obscure the prototypal inheritance at the core of JS.
  • Classes encourage inheritance but you should prefer composition.
  • Classes tend to lock you into the first bad design you came up with.

I think it’s great that the JavaScript community is paying attention to the problems caused by the use of classes and inheritance, but I’m worried that the beginners are confused as classes are both “bad” and were just added to the language. Even more confusingly, some libraries, notably React, use ES6 classes all over its documentation. Is React intentionally following “bad practices”?

I think we’re in a weird transitional period where the widespread usage of classes is a necessary evil because they are limiting. They are certainly better than learning a new ad-hoc class system that comes with every framework, each with its own way of multiple inheritance in form of mixins.

If you like functional programming, you might see how transitioning from proprietary class systems to the “stripped down” ES6 classes (no mixins, no autobinding, etc) moves us a step closer towards the functional solutions.

In the meantime, here is how to use classes and sleep at night:

  • Resist making classes your public API. (Of course exporting React components is an exception as they aren’t used directly.) You can always hide your classes behind the factory functions. If you expose them, people will inherit from them in all sorts of ways that make zero sense to you, but that you may break in the future. Avoiding breaking people’s classes is hard because they might use the method names you want to use in the future versions, they might read your private state, they might put their own state onto your instances, and they might override your methods without calling super which may work for a while if they’re lucky but break later. That said, when there isn’t back-and-forth interaction between the base class and the user’s derived classes, such as in case of React components, exposing a base class may very well be a valid API choice.
  • Don’t inherit more than once. Inheritance can be handy as a shortcut, and inheriting once is um-kay, but don’t go deeper. The problem with inheritance is that the descendants have too much access to the implementation details of every base class in the hierarchy, and vice versa. When the requirements change, refactoring a class hierarchy is so hard that it turns into a WTF sandwich with traces of outdated requirements. Instead of creating a class hierarchy, consider creating several factory functions. They may call each other in chain, tweaking the behavior of each other. You may also teach the “base” factory function to accept a “strategy” object modulating the behavior, and have the other factory functions provide it. Regardless of the approach you choose, the important part is to keep inputs and outputs explicit at every step. “You need to override a method” is not an explicit API and is hard to design well, but “you need to provide a function as an argument” is explicit and helps you think it through.
  • Don’t make super calls from methods. If you override a method in a derived class, override it completely. Tracing a sequence super calls is like following a series of notes hidden around the world by a movie villain. It’s only fun when you watch someone else doing it. What if you really need to transform the result of the super method, or perform it before or after doing something else? See the previous point: turn your classes into the factory functions and keep the relationships between them very explicit. When your only tools are parameters and return values, it’s easier to discover the right balance of responsibilities. The intermediate functions can have different (and more fine-grained) interfaces than the “top-level” and “bottom-level” functions. Classes don’t easily provide such mechanisms because you can’t designate a method “for the use of base class only” or “for the use of derived class only”, but functional composition makes it natural.
  • Don’t expect people to use your classes. Even if you choose to provide your classes as a public API, prefer duck typing when accepting inputs. Instead of instanceof checks, assert the existence of the methods you plan to use, and trust the user to do the right thing. This will make userland extensions and later tweaks easier, as well as eliminate the issues with iframes and different JavaScript execution contexts.
  • Learn functional programming. It will help you not think in classes, so you won’t be compelled to use them even though you know their pitfalls.

So What About React Components?

I hope that the rules above show why this is a terrible idea:

import { Component } from 'react';
class BaseButton extends Component {
componentDidMount() {
console.log('hey');
}
  render() {
return <button>{this.getContent()}</button>;
}
}
class RedButton extends BaseButton {
componentDidMount() {
super.componentDidMount();
console.log('ho');
}
  getContent() {
return 'I am red';
}
  render() {
return (
<div className='red'>
{super.render()}
</div>
);
}
}

However I say that the code below is just fine.

import { Component } from 'react';
class Button extends Component {
componentDidMount() {
console.log('hey');
}
  render() {
return (
<div className={this.props.color}>
<button>{this.props.children}</button>
</div>
);
}
}
class RedButton extends Component {
componentDidMount() {
console.log('ho');
}
  render() {
return (
<Button color='red'>
I am red
</Button>
);
}
}

Yes, we used the dreaded class keyword but we didn’t create a hierarchy, as we always extended Component. And you can write lint rules for that, if you wish so. There is no need to jump through the hoops just to avoid using the class keyword in the code above. It’s not a real issue.

When you want to supply a component with some additional functionality in a generic way, higher order components cover pretty much every use case I have encountered so far. Technically they are just higher order functions.

After discovering higher order components, I haven’t felt the need for either createClass()-style mixins, proposed-for-ES7 mixins, “stamp composition”, or any other composition solution. This is another argument in favor of just using class — because you don’t realistically need anything “more powerful”.

As of React 0.14 you can write the components as pure functions. This is totally worth doing. Any time you can write a function instead of a class, do.

However, when you need the lifecycle hooks or state, until React settles on some purely functional solution, I see no harm in using classes given that you don’t break the rules above. In fact it will be easier to migrate from ES6 classes to a purely functional approach than from anything else.

I am thus concerned about using “compositional solutions” that don’t directly harness React’s composition model, as in my view it’s a step back and conceptually is closer to mixins that don’t make sense in the functional paradigm.

So what are my recommendations for React components?

  • You can use class in your JS if you don’t inherit twice and don’t use super.
  • Prefer to write React components as pure functions when possible.
  • Use ES6 classes for components if you need the state or lifecycle hooks.
  • In this case, you may only extend React.Component directly.
  • Give your feedback to the React team on the functional state proposals.

Sleep well!