React ES6 Components & Static Declarations

If you've been taking advantage of ES6 and you build React Applications, the way you write components has probably changed a few times.

Unless you adopted React pretty recently, you've written some ES5 components that looked something like this.

var React = require('react')

var Component = React.createClass({  
  displayName: 'Component',
  defaultPropTypes: function() {
    return {
      firstProp: true,
      secondProp: 'yo!'
    }
  },
  getInitialState: function() {
    return {
      internalState: true
    }
  },
  propTypes: {
    firstProp: React.PropTypes.bool,
    secondProp: React.PropTypes.string
  },
  firstMethod() {
    console.log(this.state)
  },
  render() {
    return (
      <p>{this.props.secondProp}</p>
    )
  }

})

module.exports = Component  

I started playing around with React around the same time I discovered Babel. Transpiling my code was nice right away because I got to use arrow functions, the spread operator and a bunch of other cool things I had been looking forward to but the other upside was that I could write more concise code and hopefully reduce boilerplate code.

With Babel, my components looked more like this.

import React, { Component as C, PropTypes as T } from 'react'

class Component extends C {  
  constructor(props) {
    super(props)
    this.state = {
      internalState: true
    }
    this.firstMethod = this.firstMethod.bind(this)
  }
  firstMethod() {
    console.log(this.state)
  }

  render() {
    return(
       <p>{this.props.secondProp}</p>
    )
  }
}
Component.displayName = 'Component'  
Component.propTypes = {  
  firstProp: T.bool,
  secondProp: T.string
}
Component.defaultProps = {  
  firstProp: true,
  secondProp: 'yo!'
}
export default Component  

This was an improvement on the way we declared our component but I had lost a few good things. With this new syntax, our class methods are not automatically bound to the context of our class. This is why you see this line -
this.firstMethod = this.firstMethod.bind(this)

Another change that I didn't favor was declaring things like the display name, and prop types below the component. This made it difficult to check what props I was to expect because I had to stop what I was doing and scroll down if my component got big enough.

So after exploring the new class keyword, I found a few ways to fix these issues. I won't cover all of class and what it comes with, but it's worth reading over and understanding the differences between classes and regular objects. Also, it is worth noting that creating classes and extending them in the way we explore for React Components is something that should be used with care. I only recommend extending library classes or ones that you can guarantee will never change. If you have more questions about this, check this out and let me know what you think.

So with that said, let's address the first issue, no auto-binding.

There are a couple of ways that I found in which you can bind your class methods to the correct context writing ES6 React components.

First, you could bind each method manually when you call it.

<ChildComponent handleSomething={this.firstMethod.bind(this)} />  

This can be annoying and error prone. Another approach which I like much better is to use the ES6 arrow function to bind at declaration.

firstMethod = () => {  
  console.log(this.state)
}

This will bind the method right when you declare it, so now you can just call this method normally as you would anywhere else.

Here's a bonus way to solve this, you can use Babel with ES7 Experimental Mode that shipped with Babel 5.4 to take advantage of the function bind syntax.

::this.firstMethod

The 2 colons in front of the method bind the function to our context. You can do this in the way we declared bound methods in our constructor, or just when you call a function.

So now we have a lot of ways to bind our class methods, we just need a good way to declare our defaults in our component.

One way I found to solve this was to use static methods. Static methods allow us to declare methods that are called on a class without instantiating it and then become un-callable when you do instantiate that class.

What's that even mean?

Basically, what we're saying is that you can use static methods to make things that you're going to use internally in your class but don't want to be used externally by someone who is creating an instance of that class by calling new YourClass().

It's important to note that your version of Babel needs to support ES7 in order for this feature to work.

So with this new knowledge, my components started looking more like this

import React, { Component as C, PropTypes as T } from 'react'

class Component extends C {  
  static displayName = 'Component'

  static propTypes = {
    firstProp: T.bool,
    secondProp: T.string
  }

  static defaultProps = {
    firstProp: true,
    secondProp: 'yo!'
  }

  constructor(props) {
    super(props)
  }

  state = {
    internalState: true
  }

  firstMethod = () => {
    console.log(this.state)
  }

  render() {
    return(
       <p>{this.props.secondProp}</p>
    )
  }
}

export default Component  

Now we can use our fancy new JS tools while still writing our components in an easy to read way. If you like writing your components this way and you use Atom, you can go to Atom > Open Your Snippets to open your snippets.cson file and paste this in

'.source.js':  
  'React ES6':
    'prefix': 'ReactComp'
    'body': """
      import React, { Component as C, PropTypes as P } from 'react'

      class ComponentName extends C {
        static displayName = "ComponentName"

        constructor(props) {
          super(props)
        }
        static defaultProps = {
          firstProp: true,
          secondProp: 'yo!'
        }
        static propTypes = {
          firstProp: P.bool,
          secondProp: P.string
        }

        state = {
          internalState: true
        }

        firstMethod = () => {
          console.log(this.state)
        }

        render() {
          return (
            <p>{this.props.secondProp}</p>
          )
        }
      }

      export default ComponentName
    """

You can change how you access your snippet by changing the prefix field. In my case, you can write ReactComp and hit Tab to print your snippet in any file.

Happy Building!