This lesson is for PRO members.

Unlock this lesson NOW!
Already subscribed? sign in

Building a React.js App - ES6 Refactor: createClass to JavaScript Classes and propTypes

9:15 React lesson by

In this video, we’ll walk through refactoring all of our note components to learn how to go from createClass to utilize JavaScript classes to build our React components as well as how to handle propTypes with ES6.

Get the Code Now
click to level up

egghead.io comment guidelines

Avatar
egghead.io

In this video, we’ll walk through refactoring all of our note components to learn how to go from createClass to utilize JavaScript classes to build our React components as well as how to handle propTypes with ES6.

Avatar
Alex

This video and the next one can be clearly cut out of this series. They do not bring anything new except for the fact that we do not need commas inside class definitions to separate our method definitions (which could be tacked onto the previous video).

It just seems like an extra that wastes time. Students can clearly go through the code and do the repetitive things on their own. I'd rather see more useful content :-)

Overall, I find this series very useful and easy to follow. Great job!

In reply to egghead.io
Avatar
Nils

What is the benefit of using es6 class instead of React.createClass ?

Avatar
Tyler

That is a question that the JavaScript community is kind of up in arms about right now. I personally use createClass still as I'm not a huge fan of classes in JS. However, a lot of really smart people use class. Here's a pretty good article describing the pros and cons. http://reactjsnews.com/composing-components/

In reply to Nils
Avatar
egghead.io

The lesson video has been updated!

Avatar
Mac Ryan

@Alex - I see your point but disagree.

Having already looked quite a bit into React before following this series, I was surprised the series itself did not started with the ES6 syntax directly, but then I realised that possibly my case (back-end dev recently got more involved in front-end) is not the typical one.

I see that while for me ES6 is just a superior syntax, very similar (or heavily influenced?) by python, for many of my seasoned frond-end colleagues is confusing.

I think the author made a clever decision in starting with ES5 (so that most people will feel "right at home") but then moving to ES6 (if you are picking up React, you are going to write new code, there is no reason whatsoever to use legacy syntax).

Full disclosure: in following the lessons I already used ES6 along the way [and dropped the firething site in favour of redux].

In reply to Alex
Avatar
Mike

I agree, not everyone is an ES6 expert yet (myself included, for sure), so seeing this video being done with "traditional" ES5, and then refactored to ES6 was immensely helpful.

In reply to Mac Ryan
Avatar
Ben Turner

Was puzzled a bit how your app kept working in the first half of the exercise, whilst mine was exploding (due to the lack of "const" keyword in NotesList) - perhaps you had caching / some other confusion your end ? In any case, I spent quite some time debugging before just slogging through the rest of the video with a broken app, and was pleased to see the fix come later on - but some warning to other people "playing along at home" whilst watching might be good, else they end up getting lost down similar rabbit holes.

In reply to Mike
Avatar
Tyler

I agree. This was one of those moments I didn't catch until the videos were published. Once I do another update I'll add a note in that not having const just breaks everything but it does get fixed later in the video.

In reply to Ben Turner
Avatar
Jori

Hi. First off, great series! A question though: why do we have to bind propTypes seperately to a class?

Avatar
Tyler

Good question. class definitions in ES6 only allow you to define methods and not properties. Hence why we can't put propTypes by our methods in our class and instead need to be added after.

In reply to Jori
Avatar
Derek Hannah

Id advise against teaching the es6 class syntax, its harmful to all of us. We need to prefer composition over classical inheritance. https://medium.com/javascript-scene/the-two-pillars-of-javascript-ee6f3281e7f3#.3ab6fv5ob

Avatar
Derek Hannah

also Peter Hunt of the Facebook React team has told me personally that they do not use the es6 class syntax at Facebook

In reply to Derek Hannah
Avatar
Derek Hannah

other than that this whole course was dope thanks!

In reply to Derek Hannah
Avatar
Joel

Id advise against teaching the es6 class syntax, its harmful to all of us. We need to prefer composition over classical inheritance. https://medium.com/javascript-scene/the-two-pillars-of-javascript-ee6f3281e7f3#.3ab6fv5ob

It's really not, all due respect to Mr Elliott, the es6 class syntax is the least of our problems.

In reply to Derek Hannah
Avatar
Tyler

Just in case any beginners read this and get stressed out, I'd like to say I think it's more than fine to use ES6 classes for React. Dan is smarter than I am though and here's his opinion. https://medium.com/@dan_abramov/how-to-use-classes-and-sleep-at-night-9af8de78ccb4#.m2ph4tq8i

In reply to Derek Hannah
Avatar
Kirk Sefchik

The moment I start to refactor NotesList, I start getting the following error:
```

Warrning: React.createElement: type should not be null, undefined, boolean, or number. It should be a string (for DOM elements) or a ReactClass (for composite components). Check the render method of Notes.

Anyone have any idea why this is happening? It triggers as soon as I convert NotesList from `React.createClass` syntax to ES6 class syntax.
Avatar
Peter

I have exactly the same problem, any one knows what the reason is for that? Checked all the code and is exactly the same way as in the video.

In reply to Kirk Sefchik
Avatar
Robert Misior

You need to change the Notes to use import syntax for the NotesList:

import NotesList from './NotesList';

In reply to Kirk Sefchik

Let's go ahead and start refactoring some of our components. In this video, we are going to refactor all of the components in our notes folder, so let's go ahead and start with NotesList.

The very first thing, instead of require, we need to import react from 'react'.

Notes/NotesList.js

import React from 'react';

Once we've done that, now what we need to do is you'll notice here we're using react.createClass. Instead, let's go ahead and use the built-in ES6 class syntax.

We're going to go class, and the name of our class. We're going to have it extend react.Component, and let's go ahead and capitalize Component. Now we've created our class, and real quick, let's go ahead and export default NotesList.

Notes/NotesList.js

class NotesList extends React.Component{

}

export default NotesList;

What we're going to do now is have our render method. Remember, we don't need to type out function. Then I'm just going to copy this and throw it into here. The next thing we're going to do is let's go ahead and remove function and use our arrow syntax, as we talked about before.

Notes/NotesList.js

class NotesList extends React.Component{
  render(){
    var notes = this.props.notes.map((note, index) => {
      return <li className="list-group-item" key={index}>{note['.value']}</li>
    })

    return (
      <ul className="list-group">
        {notes}
      </ul>
    )
  }
}

Throughout this series, what we've done to loop over any lists is we've mapped outside of our return block here and then saved it into a variable, and then used our variable down here. I want to show you a little bit different syntax, just because you might see it.

I'm going to go ahead and take this map function and I'm going to throw it in right there, and then just fix this indention.

Notes/NotesList.js

<ul className="list-group">
  {this.props.notes.map((note, index) => {
    return <li className="list-group-item" key={index}>{note['.value']}</li>
  })}
</ul>

This is technically the same thing as before, but instead of saving it to a variable, we're now actually just looping inside of our template.

Even take this a little bit further, so what I'm going to do is I'm going to destructure this.props. What that means is I can do something like this.

What that's done is it's taken the notes property on this.props, which is this guy right here, and saved it into a variable called notes. I can get rid of this line right here, so notes.map, and now what I can do -- and I could have done this before, but I'm just showing you now -- is I'm going to go ahead and use the implicit return here to keep this all on one line like that. Then we get something like this.

Notes/NotesList.js

render(){
  { notes } = this.props;
  return (
    <ul className="list-group">
      {notes.map((note, index) => <li className="list-group-item" key={index}>{node['.value']}</li>)}
    </ul>
  )
}
}

I'm not a huge fan of that, just because it seems a little bit scrunched just to force it to be on one line, but you've seen how powerful arrow syntax can be, and you've also seen this new idea of destructuring variables.

Let's see and make sure this still works. NotesList is still going fine, so cool. There is our NotesList component.

The last change I'm going to make is, if you'll notice, this is how we had it before. If you use curly braces, then you need to have an explicit return here.

Notes/NotesList.js

render(){
  { notes } = this.props;
  return (
    <ul className="list-group">
      {notes.map((note, index) => {
          return <li className="list-group-item" key={index}>{node['.value']}</li>)
        }
      }
    </ul>
  )
}
}

But what some people do -- I'm just doing this so you can see all the different patterns -- is if you put a paren right here and not a curly brace, you can still...let's move this down here, and then we need two of these. Those match up, and then these should match up.

You can still take advantage of that implicit return, as long as you don't have a curly brace here. Some people really like this syntax. I'm not a huge fan of it, but it should still work, as well. Let's see.

Notes/NotesList.js

render(){
  const { notes } = this.props;
  return (
    <ul className="list-group">
      {notes.map((note, index) => (
          <li className="list-group-item" key={index}>{node['.value']}</li>
        ))}
    </ul>
  )
}
}

There's our notes component still rendering. I'm going to keep it like this, but you've seen the different patterns that people use for looping over items in a react component.

Let's head over to our AddNote component, so same thing. Instead of that, we're going to import react from 'react'. We're going to create a class called AddNote, which extends React.Component, export default, AddNote.

Notes/AddNote.js

import React from 'react';

class AddNote extends React.Component {
  render() {

  }
}

export default AddNote

Let's go ahead and move our render method up, so here, we type render, which returns this UI. Add a handleSubmit method. Notice here, we don't have to put commas in between our methods because we're technically not in an object, so I'm going to move this up, as well.

Notes/AddNote.js

class AddNote extends React.Component {
  handleSubmit(){
    var newNote = this.note.value;
    this.note.value = '';
    this.props.addNote(newNote);
  }

  render(){
    return(
      <div className="input-group">
        <input type="text" className="form-control" placeholder="Add New Note" ref={this.setRef}/>
        <span className='input-group-btn'>
          <button className="btn btn-default" type="button" onClick={this.handleSubmit}>Submit</button>
        </span>
      </div>
    )
  }
}

One tricky thing that we need to do is when you're using React.createClass, that this keyword is automatically auto-bound to the correct context for you, which makes it really convenient. But one got-you you're going to have over and over again with ES6 classes in react is that this keyword is not auto-bound.

We want this keyword inside of this function to be the same as this keyword, so what we're going to do is just a handlesubmit.bind(this), so that this keyword inside of this context is the correct one. That looks good, so now the only thing left we need to do is worry about propType.

Notes/AddNote.js

render(){
  return(
    <div className="input-group">
      <input type="text" className="form-control" placeholder="Add New Note" ref={this.setRef}/>
      <span className='input-group-btn'>
        <button className="btn btn-default" type="button" onClick={this.handleSubmit.bind(this)}>Submit</button>
      </span>
    </div>
  )
}

What's a little bit different about propTypes, too, is you have to add them to the class itself, so we're going to come up here and type AddNote, and as a property on AddNote, have our propTypes object, and that should be it for this component.

Notes/AddNote.js

class AddNote extends React.Component {...}

AddNote.propTypes = {
  username: React.PropTypes.string.isRequired,
  addNote: React.PropTypes.func.isRequired
}

One small fix real quick is to set this equal to the object instead of having a colon, so set this to equals. Now we just need to do a few more things.

First, let's go ahead and fix our setRef function, and delete all this junk down here. Get rid of the function keywords. We have setRef now.

Notes/AddNote.js

setRef(ref){
  this.note = ref;
}

What we could do is we could come in here and do .bind(this) again, because remember that this keyword is going to be different. But another pattern that I really like is instead of doing .bind, what we can do is we can create a arrow function right here and then call this.setRef. Then we're going to get a ref, and then let's pass that there.

Notes/AddNote.js

render(){
  return(
    <div className="input-group">
      <input type="text" className="form-control" placeholder="Add New Note" ref={(ref) => this.setRef(ref)}/>
      <span className='input-group-btn'>
        <button className="btn btn-default" type="button" onClick={() => this.handleSubmit()}>Submit</button>
      </span>
    </div>
  )
}

Remember, arrow functions don't create their own context. this keyword inside this arrow function is the exact same as the one out here, so this is actually the preferred way that I like to get around the auto-binding issue. Instead of using .bind, I just like to create these arrow functions. Then we'll invoke handleSubmit, and then this function will run.

This looks good. Let's go ahead and check webpack. Let's start webpack, and it looks like we have an issue. That's back in NotesList, so come here.

You can't destructure like this. You obviously need to assign it a variable type, so we have const. That looks good.

What we need to do is every time we change a component, we need to go and figure out where that component is being required and change that, as well. Let's go ahead, and everywhere we're using AddNote, which is right here, so we're going to import AddNote from AddNote, and then we also change NotesList.

Notes/Notes.js

var React = require('react');
import NotesList from './NotesList';
import AddNote from './AddNote';

Let's go ahead and do that one, as well, and then let's check these files. I think we're good. Let's see if webpack's good. Let's see if we have any errors. So far, so good.

Let's go ahead and modify the last file, our Notes.js file. I swapped out our require for some imports. Now let's go ahead and create a class Notes which extends React.Component, and then we're going to export default Notes.

Notes/Notes.js

import React from require('react');
import NotesList from './NotesList';
import AddNote from './AddNote';

class Notes extends React.Component {
  render(){
    return(
      <div>
        <h3> Notes for {this.props.username} </h3>
        <AddNote username={this.props.username} addNote={this.props.addNote} />
        <NotesList notes={this.props.notes} />
      </div>
    )
  }
}

export default Notes;

Let's go ahead and move our render method up, so that looks good. Now the only thing left to do is our propTypes.

Notes/Notes.js

Notes.propTypes = {
  username: React.PropTypes.string.isRequired,
  notes: React.PropTypes.array.isRequired,
  addNote: React.PropTypes.func.isRequired
}

Again, the very last thing is now we need to figure out where we're importing Notes, or where we're requiring it, and modify that. Let's go ahead and change in our Profile.js component to our file, import Notes from Notes, from this. There we go.

components/Profile.js

var React = require('react');
var Router = require('react-router');
var Repos = require('./Github/Repos');
var UserProfile= require('./Github/UserProfile');
import Notes from './Notes/Notes';
var ReactFireMixin = require('reactfire');
var Firebase = require('firebase');
import getGithubInfo from '../utils/helpers';
HEY, QUICK QUESTION!
Joel's Head
Why are we asking?