React 15.5 Components: Part 1

React 15.5 is out! Hurray!! Oh wait…they don’t like React.createClass any more. Well, that’s a bummer.

It is unfortunate to not have auto-binding anymore and it seems like my new components are noticeably larger, but progress waits for no one. Let’s convert our so last week React.createClass components to the preferred ES6 idiom.

To make it easier to discuss, I’ll show the before and after and then talk about the steps taken. That said, this is still going to be a boring “recipe” post.

React.createClass()

Let’s come up with a nifty component that has a basic structure. I’ll re-use this in my prop-types to Flow example later.

import React from 'react'

const { PropTypes } = React

const NiftyComponent = React.createClass({
  propTypes: {
    onClick: PropTypes.func,
    values: PropTypes.arrayOf(PropTypes.number)
  },

  getInitialState() {
    return {
      items: []
    }
  },

  render() {
    return (
      <div>
        <a onClick={this.handleClick}>Click Me!</a>
        <a onClick={this.props.onClick.bind(null, args)}>
      </div>
    )
  },

  handleClick(event) {
    // something happens
  }
})

A silly component that has some props, state, two click handlers for demonstration purposes, and not much else.

ES6 Class After Conversion

import React from 'react'

const { PropTypes } = React

class NiftyComponent extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      items: []
    }
    this.handleClick = this.handleClick.bind(this)
  }

  render() {
    return (
      <div>
        <a onClick={this.handleClick}>Click Me!</a>
        <a onClick={e => this.props.onClick(args)}>
      </div>
    )
  }

  handleClick(event) {
    // something happens
  }
}

NiftyComponent.propTypes = {
  onClick: PropTypes.func,
  values: PropTypes.arrayOf(PropTypes.number)
}

As with most things in programming, I can get used to how this looks.

Steps to “Upgrade”

Let’s enumerate the changes you have to make:

const to class

This is the obvious change, moving from the helper function to the idiomatic ES6 class.

const NiftyComponent = React.createClass({

and after…

class NiftyComponent extends React.Component {

No big deal, just remember to clean up your parentheses.

Move prop-types out

Ah yeah, now the prop-types have to move out.

NiftyComponent.propTypes = {
  onClick: PropTypes.func,
  values: PropTypes.arrayOf(PropTypes.number)
}

Add constructor and bindings

If we have any sort of handlers that we want to interact with the same scope as the rest of our class, we have to bind them in a constructor:

constructor(props) {
  super(props)
  this.handleClick = this.handleClick.bind(this)
}

Fix manual binds

Sometimes I like to pass arguments into my handler functions, which means I have to call them with an arrow function. Let’s pretend we had something that could change like an input, and we were provided a prop onChange. The old way would be:

<input onChange={this.props.onChange.bind(null, args)}>

to:

<input onChange={e => this.props.onChange.bind(args)}>

There is a discussion on the trade-offs on using the arrow function in the docs. The feedback I’ve received from designers thus far is that they prefer arrow functions as the component gets a lot of boilerplate from the binds in the constructor approach. If you can update your babelrc, the property initializer approach is appealing.

Fix state

Since we don’t getInitialState anymore, we have to move the initial assignment into the constructor:

  getInitialState() {
    return {
      items: []
    }
  }

becomes…

  constructor(props) {
    super(props)
    this.state = {
      items: []
    }
  }

Others?

Honestly, there might be more changes that I had to make, but these are the ones coming to mind right now. I’ll handle prop-types changes in the next post as that can be a huge deal if you want to embrace the future.

What about jscodeshift?

If you read the post, you’ll know that there’s an automated code changer that you can use. YMMV, but for me, it broke all my components by partially converting them. I think it was because my babel config didn’t have support for the experimental function declaration:

handleClick = () => {
  // stuff
}

Ideally, you can modify your .babelrc to include es2016 property initializer syntax but if you can’t, you’ll have to fix the above by hand. Here’s the directions from the blog post:

jscodeshift -t react-codemod/transforms/class.js path/to/components

Of course, you need to install a couple things to get this to work, change it to reference the exact path to where react-codemod exists, but it might be worth it.

I’d recommend making the changes by hand, but that doesn’t scale to 30k components, so do as thou whilst.

Published under programming, twitter

Related Posts

Similar to my last post on converting to ES6 Classes, this will cover the “recipe” for moving from prop-types in a React component to Flow, a static type checker for Javascript.