119

I have the following component (radioOther.jsx):

 'use strict';

 //module.exports = <-- omitted in update

   class RadioOther extends React.Component {

     // omitted in update 
     // getInitialState() {
     //    propTypes: {
     //        name: React.PropTypes.string.isRequired
     //    }
     //    return {
     //       otherChecked: false
     //   }
     // }

     componentDidUpdate(prevProps, prevState) {
         var otherRadBtn = this.refs.otherRadBtn.getDOMNode();

         if (prevState.otherChecked !== otherRadBtn.checked) {
             console.log('Other radio btn clicked.')
             this.setState({
                 otherChecked: otherRadBtn.checked,
             });
         }
     }

     onRadChange(e) {
         var input = e.target;
         this.setState({
             otherChecked: input.checked
         });
     }

     render() {
         return (
             <div>
                 <p className="form-group radio">
                     <label>
                         <input type="radio"
                                ref="otherRadBtn"
                                onChange={this.onRadChange}
                                name={this.props.name}
                                value="other"/>
                         Other
                     </label>
                     {this.state.otherChecked ?
                         (<label className="form-inline">
                             Please Specify:
                             <input
                                 placeholder="Please Specify"
                                 type="text"
                                 name="referrer_other"
                                 />
                         </label>)
                         :
                         ('')
                     }
                 </p>
             </div>
         )
     }
 };

Prior to using ECMAScript6 all was well, now I am getting 1 error, 1 warning and I have a followup question:

Error: Uncaught TypeError: Cannot read property 'otherChecked' of null

Warning: getInitialState was defined on RadioOther, a plain JavaScript class. This is only supported for classes created using React.createClass. Did you mean to define a state property instead?


  1. Can anyone see where the error lies, I know it is due to the conditional statement in the DOM but apparently I am not declaring its initial value correctly?

  2. Should I make getInitialState static

  3. Where is the appropriate place to declare my proptypes if getInitialState is not correct?

UPDATE:

   RadioOther.propTypes = {
       name: React.PropTypes.string,
       other: React.PropTypes.bool,
       options: React.PropTypes.array }

   module.exports = RadioOther;

@ssorallen, this code :

     constructor(props) {
         this.state = {
             otherChecked: false,
         };
     }

produces "Uncaught ReferenceError: this is not defined", and while below corrects that

     constructor(props) {
     super(props);
         this.state = {
             otherChecked: false,
         };
     }

but now, clicking the other button now produces error:

Uncaught TypeError: Cannot read property 'props' of undefined

Talha Awan
  • 4,573
  • 4
  • 25
  • 40
  • 1
    The other change for ES6 classes is that methods are not "auto-bound" to the instance, meaning when you pass a function like `onChange={this.onRadChange}`, `this` does not refer to the instance when `onRadChange` is called. You need to bind callbacks in `render` or do it in the constructor: `onChange={this.onRadChange.bind(this)}`. – Ross Allen Jun 09 '15 at 02:35

3 Answers3

242
  • getInitialState is not used in ES6 classes. Instead assign this.state in the constructor.
  • propTypes should be a static class variable or assigned to the class, it should not be assigned to component instances.
  • Member methods are not "auto-bound" in ES6 classes. For methods used as callbacks, either use class property initializers or assign bound instances in the constructor.
export default class RadioOther extends React.Component {

  static propTypes = {
    name: React.PropTypes.string.isRequired,
  };

  constructor(props) {
    super(props);
    this.state = {
      otherChecked: false,
    };
  }

  // Class property initializer. `this` will be the instance when
  // the function is called.
  onRadChange = () => {
    ...
  };

  ...

}

See more in the React's documentation about ES6 Classes: Converting a Function to a Class

Ross Allen
  • 43,772
  • 14
  • 97
  • 95
  • A year on and now you might find you get : `Uncaught TypeError: Cannot read property 'bind' of undefined` error Any ideas? – Jamie Hutber Jun 21 '16 at 23:42
  • @JamieHutber Can you create a new question with your code? If the method is defined on the class, I don't know why you would get that error. – Ross Allen Jun 22 '16 at 02:41
  • Thanks for the suggestion, @KennethWorden. I opened an issue for the React docs: https://github.com/facebook/react/issues/7746 – Ross Allen Sep 15 '16 at 23:12
  • Does 'bind them in place' mean use an arrow function as you show above? That does work, of course. Oh, javascript! – jomofrodo Nov 30 '16 at 17:56
  • @jomofrodo That is ambiguous, thanks for pointing that out. I meant in `render` you could do something like `onClick={this.doFoo.bind(this)}`, but I intentionally didn't include it in the code because that's the least efficient way to bind since `render` may be called often and would create new functions on each call. I will remove that language from the answer. – Ross Allen Dec 23 '16 at 06:54
5

Adding to Ross's answer.

You could also use the new ES6 arrow function on the onChange property

It is functionally equivalent to defining this.onRadChange = this.onRadChange.bind(this); in the constructor but is more concise in my opinion.

In your case the radio button will look like the below.

<input type="radio"
       ref="otherRadBtn"
       onChange={(e)=> this.onRadChange(e)}
       name={this.props.name}
       value="other"/>

Update

This "more concise" method is less efficient than the options mentioned in @Ross Allen's answer because it generates a new function each time the render() method is called

HoldOffHunger
  • 18,769
  • 10
  • 104
  • 133
Sudarshan
  • 8,574
  • 11
  • 52
  • 74
  • 1
    This solution is functionally correct, but it creates a new Function on each call to `render` (which could be called many times). By using a class property initializer or binding in the constructor, only one new bound Function is created on construction of the component and no new Functions are created during `render`. – Ross Allen Jan 11 '17 at 00:04
1

If you are using babel-plugin-transform-class-properties or babel-preset-stage-2 (or stage-1, or stage-0), you can use this syntax:

class RadioOther extends React.Component {

  static propTypes = {
    name: React.PropTypes.string,
    ...
  };

  state = {
      otherChecked: false,
  };

  onRadChange = () => {
    ...
  };

  ...

}
Evgenia Karunus
  • 10,715
  • 5
  • 56
  • 70