Michal Zalecki
Michal Zalecki
software development, testing, JavaScript,
TypeScript, Node.js, React, and other stuff

React components and class properties

React components went the long way from React.createClass through ES2015 powered React.Component to React.PureComponent and stateless functional components. I really enjoy the idea that we don't have to "hack" the language anymore, at least not that much as we used to. The progress in this department is quite clear and brings not always obvious benefits. Using constructs built into the language/transpiler instead of relying on framework's factory functions or constructors accepting huge configuration objects future proofs your code.

With Babel or TypeScript future is already here or comes down to your risk aversion for transpiling your code with stage-N preset. Stage 2 is a proposal likely to be included in the new standard. It brings class fields and static properties. React components can greatly benefit from them, both when it comes to performance and reducing noise in your code.

Initializing state

As you should prefer stateless components over classes, sometimes you need to persist some state. To initialize state in your React component we are used to doing this in the constructor.

// BEFORE
import React, { Component } from "react";

class Counter extends Component {
  constructor(props) {
    super(props);

    this.state = { counter: 0 };
  }

  ...
}

Initializing state inside of the constructor comes with an overhead of calling super and remembering about props which are React's abstraction which leaks here a little. We can initialize state directly as a class property.

// AFTER
import React, { Component } from "react";

class Counter extends Component {
  state = { counter: 0 };

  ...
}

Due to the fact that the state is defined on the instance, we still have access to this during state initialization. This makes it possible to use props to initialize the state. Let's, for example, specify the number from which counter starts.

// AFTER
import React, { Component } from "react";

class Counter extends Component {
  state = { counter: this.props.startsWith };

  ...
}

Bounded methods

It is common for event handlers to modify state. To do that handler has to be called in the component's context which is not going to happen by default. The common source of the performance problems is passing event handlers with a bounded state with arrow function or calling .bind(this) on the event handler inside the render() call. Each of those techniques causes creation of new function inside the render killing PureComponent/PureRenderMixin optimizations. The right way to approach this problem is binding your event handler inside of the component's constructor.

// BEFORE
import React, { Component } from "react";

class Counter extends Component {
  state = { counter: 0 };

  constructor(props) {
    super(props);

    this.onIncrementClick = this.onIncrementClick.bind(this);
  }

  onIncrementClick() {
    this.setState(this.increment);
  }

  increment(state) {
    return { ...state, counter: state.counter + 1 };
  }

  render() {
    return <button onClick={this.onIncrementClick}>{this.state.counter}</button>;
  }
}

We can benefit from the fact that arrow function preserves context in which was defined and set handler directly as a class field.

// AFTER
import React, { Component } from "react";

class Counter extends Component {
  state = { counter: 0 };

  onIncrementClick = () => {
    this.setState(this.increment);
  }

  increment(state) {
    return { ...state, counter: state.counter + 1 };
  }

  render() {
    return <button onClick={this.onIncrementClick}>{this.state.counter}</button>;
  }
}

I recommend you to take it further with memoization and partial application if you are into functional programming.

Static fields

There are three common cases where static fields shine when it comes to React components. It is setting propsTypes, defaultProps and childContextTypes. We are used to setting them outside of the class. It often makes you scroll to the very bottom of the file to learn about props which component expects.

// BEFORE
import React, { Component, PropTypes } from "react";

class Counter extends Component {
  ...
}

Counter.propTypes = {
  step: PropTypes.number.isRequired,
};

Defining props as static fields allows you to keep them inside of the class and benefit from a common convention of keeping statics at the top of the class.

// AFTER
import React, { Component, PropTypes } from "react";

class Counter extends Component {
  static propTypes = {
    step: PropTypes.number.isRequired,
  }
}

What is next?

I can only guess but I am sure we are going to discover great use cases for await/async, pipe operator and partial application. No matter what will come first, it is good we are ready.

Photo by Vladimir Proskurovskiy on Unsplash.