Use Property Initializers for Cleaner React Components

In our previous post on Babel plugins and presets, we mentioned that there was one particular experimental JavaScript feature that is popular in the React community: property initializers.

As you'll see, the property initializers feature is popular among React developers for good reason. It allows for a much "cleaner" ES6 class component style. Let's take a look.

⚠️ As we mentioned in the Babel plugins and presets post, property initializers are still in the proposal phase and yet to be ratified for adoption into the JavaScript spec. There is some risk that this feature will be modified or dropped. However, due to its popularity, we believe the risks are minimal.

The proposal

Property initializers are detailed in the proposal "ES Class Fields & Static Properties." While an experimental feature that has yet to be ratified, this feature works so well with React that the Facebook team has written about using it internally.

Preparation

Property initializers are available in the Babel plugin transform-class-properties. To use it, we must do two things:

  1. Install the plugin. We can use the following command to both install the plugin and ensure it's saved to our package.json:
npm install babel-plugin-transform-class-properties --save
  1. Add the following .babelrc file to our project:
// .babelrc
{
  "plugins": ["transform-class-properties"]
}

With the plugin installed and Babel configured to use it, we're ready to refactor our React ES6 class components to use this feature.

If you're still experimenting with React and using babel-standalone to transpile your JavaScript in-line, you can use the data-plugins attribute on any script tag to enable the feature:

    <script
      type="text/babel"
      data-plugins="transform-class-properties"
      src="./js/app.js"
    ></script>

babel-standalone already includes the plugin.

Refactoring a component to use property initializers

Let's say we have a counter component. This counter allows us to increment or decrement a number:

Image of the counter app

The original component

Using vanilla ES6/ES7 JavaScript, our styled component might look something like this:


class CounterApp extends Component {
  constructor() {
    super();

    this.state = {
      count: 0
    };

    this.incrementCount = this.incrementCount.bind(this);
    this.decrementCount = this.decrementCount.bind(this);
  }

  incrementCount() {
    this.setState((prevState) => (
      { count: prevState.count + 1 }
    ));
  }

  decrementCount() {
    this.setState((prevState) => (
      { count: prevState.count - 1 }
    ));
  }

  render() {
    return (
      <div className="card">
        <div className="content">
          <div className="header">
            {this.state.count}
          </div>
        </div>
        <div
          className="ui bottom attached button"
          onClick={this.incrementCount}
        >
          <i className="add icon" style={marginZeroStyle} />
        </div>
        <div
          className="ui bottom attached button"
          onClick={this.decrementCount}
        >
          <i className="minus icon" style={marginZeroStyle} />
        </div>
      </div>
    );
  }
}

The biggest hangup for most developers is that we have to bind custom component methods to the component inside constructor(). This practice is both a little verbose and easy to miss, which leads to weird errors.

If you're unfamiliar with binding strategies for ES6 class components, check out our post that compares ES6 class components and React.createClass components.

With property initializers

With property initializers, we can get rid of our constructor() function in this example component entirely.

We can declare state outside of the constructor() at the top of the component, like this:


class CounterApp extends Component {
  state = {
    count: 0
  };

Furthermore, we can use an arrow function to declare our methods incrementCount() and decrementCount(). This will ensure this is bound to the component inside of those methods, as expected:


  incrementCount = () => {
    this.setState((prevState) => (
      { count: prevState.count + 1 }
    ));
  };

  decrementCount = () => {
    this.setState((prevState) => (
      { count: prevState.count - 1 }
    ));
  };

Now we don't need to perform any manual binding.

In full, our refactored component (excluding render()):


class CounterApp extends Component {
  state = {
    count: 0
  };

  incrementCount = () => {
    this.setState((prevState) => (
      { count: prevState.count + 1 }
    ));
  };

  decrementCount = () => {
    this.setState((prevState) => (
      { count: prevState.count - 1 }
    ));
  };

In sum, we can use property initializers to make two refactors to our React components:

  1. We can define the initial state outside of constructor()
  2. We can use arrow functions for custom component methods (and avoid having to bind this)

Using ES6/ES7 with additional presets or plugins like we do in this post is sometimes referred to by the community as "ES6+/ES7+".


Because you found this post helpful, you'll love our book — it's packed with over 800 pages of content and over a dozen projects, including chapters on React fundamentals, Redux, Relay, GraphQL, and more.


Anthony Accomazzo

Passionate about teaching, Anthony has coached many beginning programmers through their early stages. Before Fullstack React, he led the development of IFTTT's API platform. A minimalist and a traveler, Anthony has been living out of a backpack for the past year.