184
class PlayerControls extends React.Component {
  constructor(props) {
    super(props)

    this.state = {
      loopActive: false,
      shuffleActive: false,
    }
  }

  render() {
    var shuffleClassName = this.state.toggleActive ? "player-control-icon active" : "player-control-icon"

    return (
      <div className="player-controls">
        <FontAwesome
          className="player-control-icon"
          name='refresh'
          onClick={this.onToggleLoop}
          spin={this.state.loopActive}
        />
        <FontAwesome
          className={shuffleClassName}
          name='random'
          onClick={this.onToggleShuffle}
        />
      </div>
    );
  }

  onToggleLoop(event) {
    // "this is undefined??" <--- here
    this.setState({loopActive: !this.state.loopActive})
    this.props.onToggleLoop()
  }

I want to update loopActive state on toggle, but this object is undefined in the handler. According to the tutorial doc, I this should refer to the component. Am I missing something?

Emile Bergeron
  • 14,368
  • 4
  • 66
  • 111
Maximus S
  • 9,379
  • 18
  • 67
  • 140

10 Answers10

241

ES6 React.Component doesn't auto bind methods to itself. You need to bind them yourself in constructor. Like this:

constructor (props){
  super(props);
  
  this.state = {
      loopActive: false,
      shuffleActive: false,
    };
  
  this.onToggleLoop = this.onToggleLoop.bind(this);

}
Amirhossein Mehrvarzi
  • 11,037
  • 6
  • 38
  • 65
Ivan
  • 14,469
  • 6
  • 20
  • 29
  • 32
    if you change your onClick property to `() => this.onToggleLoop` after moving the onToggleLoop function into your react class it will work as well. – Sam Aug 26 '16 at 18:50
  • 81
    Do you really have to bind every method of every react class? Isn't that a little crazy? – Alex L Oct 05 '16 at 19:37
  • 7
    @AlexL There are ways to do it without explicitly binding the methods. if you use babel it's possible to declare every method on React component as arrow functions. There are examples here: https://babeljs.io/blog/2015/06/07/react-on-es6-plus – Ivan Oct 06 '16 at 20:11
  • 10
    But why is `this` undefined in the first place? I know `this` in Javascript depends on how the function is called, but what's going on in here? – darKnight Jan 03 '18 at 16:26
  • 1
    but how can i do this is the function is not defined until after the constructor? I get a "Cannot read property 'bind' of undefined" if i try to do this in the constructor even thou my function is defined on the class :( – rex Jan 15 '18 at 17:01
  • @Ivan the article is actually at: https://babeljs.io/blog/2015/07/07/react-on-es6-plus and thanks for that information it was exactly what I was looking for! :D – OpensaurusRex Feb 20 '19 at 19:24
  • 1
    TLDR for the article: use arrow functions instead. – OscarRyz Jun 10 '19 at 22:17
  • I found making `onClick` point to `function.bind(this)` instead of just `function` was the least intrusive fix. I could keep my React classes the same. – Pavel Komarov Jun 25 '19 at 20:06
  • 1
    I found I had to bind my `onClick` property slightly differently to @Sam . In the example shown it would be `onClick={ () => { this.onToggleLoop(); }}` - in general im with the "Isn't this a little crazy" crowd. – Morvael Mar 18 '20 at 15:52
102

There are a couple of ways.

One is to add this.onToggleLoop = this.onToggleLoop.bind(this); in the constructor.

Another is arrow functions onToggleLoop = (event) => {...}.

And then there is onClick={this.onToggleLoop.bind(this)}.

Henrik Andersson
  • 37,809
  • 15
  • 88
  • 86
J. Mark Stevens
  • 4,687
  • 2
  • 10
  • 17
  • 3
    Why does onToogleLoop = () => {} work? I got the same problem and I bindet it in my constructor but it didnt work ... and now i have seen your post and replace my method with a arrow function syntax and it works. Can you explain it to me ? – Guchelkaben Sep 15 '17 at 06:51
  • 7
    from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions; An arrow function does not create its own this, the this value of the enclosing execution context is used. – J. Mark Stevens Sep 15 '17 at 16:34
  • 1
    Note that binding inline in the `onClick` will return a new function every render and thus it looks like a new value has been passed for the prop, messing with `shouldComponentUpdate` in `PureComponent`s. – Ninjakannon May 25 '18 at 11:34
29

Write your function this way:

onToggleLoop = (event) => {
    this.setState({loopActive: !this.state.loopActive})
    this.props.onToggleLoop()
}

Fat Arrow Functions

the binding for the keyword this is the same outside and inside the fat arrow function. This is different than functions declared with function, which can bind this to another object upon invocation. Maintaining the this binding is very convenient for operations like mapping: this.items.map(x => this.doSomethingWith(x)).

Rich Warrior
  • 1,310
  • 6
  • 17
ShaTin
  • 2,459
  • 16
  • 8
11

I ran into a similar bind in a render function and ended up passing the context of this in the following way:

{someList.map(function(listItem) {
  // your code
}, this)}

I've also used:

{someList.map((listItem, index) =>
    <div onClick={this.someFunction.bind(this, listItem)} />
)}
duhaime
  • 19,699
  • 8
  • 122
  • 154
  • 1
    That's a lot of unnecessary functions you're creating there, each and every time the list is rendered... – T.J. Crowder Feb 08 '18 at 18:38
  • @T.J.Crowder Yes it's true these functions are created afresh each time render is called. It's better to create the functions as class methods and bind them once to the class, but for beginners the manual context binding may be helpful – duhaime Feb 08 '18 at 19:16
3

You should notice that this depends on how function is invoked ie: when a function is called as a method of an object, its this is set to the object the method is called on.

this is accessible in JSX context as your component object, so you can call your desired method inline as this method.

If you just pass reference to function/method, it seems that react will invoke it as independent function.

onClick={this.onToggleLoop} // Here you just passing reference, React will invoke it as independent function and this will be undefined

onClick={()=>this.onToggleLoop()} // Here you invoking your desired function as method of this, and this in that function will be set to object from that function is called ie: your component object
Jakub Kutrzeba
  • 811
  • 8
  • 11
  • 1
    Right, you can even use the first line i.e. `onClick={this.onToggleLoop}` provided that in your component class you have defined a field (property) `onToggleLoop = () => /*body using 'this'*/` – gvlax Sep 12 '19 at 10:12
1

If you are using babel, you bind 'this' using ES7 bind operator https://babeljs.io/docs/en/babel-plugin-transform-function-bind#auto-self-binding

export default class SignupPage extends React.Component {
  constructor(props) {
    super(props);
  }

  handleSubmit(e) {
    e.preventDefault(); 

    const data = { 
      email: this.refs.email.value,
    } 
  }

  render() {

    const {errors} = this.props;

    return (
      <div className="view-container registrations new">
        <main>
          <form id="sign_up_form" onSubmit={::this.handleSubmit}>
            <div className="field">
              <input ref="email" id="user_email" type="email" placeholder="Email"  />
            </div>
            <div className="field">
              <input ref="password" id="user_password" type="new-password" placeholder="Password"  />
            </div>
            <button type="submit">Sign up</button>
          </form>
        </main>
      </div>
    )
  }

}
Henry Jacob
  • 91
  • 1
  • 2
0

If you call your created method in the lifecycle methods like componentDidMount... then you can only use the this.onToggleLoop = this.onToogleLoop.bind(this) and the fat arrow function onToggleLoop = (event) => {...}.

The normal approach of the declaration of a function in the constructor wont work because the lifecycle methods are called earlier.

Guchelkaben
  • 857
  • 1
  • 9
  • 15
0

in my case this was the solution = () => {}

methodName = (params) => {
//your code here with this.something
}
Alex
  • 6,714
  • 26
  • 84
  • 140
0

In my case, for a stateless component that received the ref with forwardRef, I had to do what it is said here https://itnext.io/reusing-the-ref-from-forwardref-with-react-hooks-4ce9df693dd

From this (onClick doesn't have access to the equivalent of 'this')

const Com = forwardRef((props, ref) => {
  return <input ref={ref} onClick={() => {console.log(ref.current} } />
})

To this (it works)

const useCombinedRefs = (...refs) => {
  const targetRef = React.useRef()

  useEffect(() => {
    refs.forEach(ref => {
      if (!ref) return

      if (typeof ref === 'function') ref(targetRef.current)
      else ref.current = targetRef.current
    })
  }, [refs])

  return targetRef
}

const Com = forwardRef((props, ref) => {
  const innerRef = useRef()
  const combinedRef = useCombinedRefs(ref, innerRef)

  return <input ref={combinedRef } onClick={() => {console.log(combinedRef .current} } />
})
GWorking
  • 3,166
  • 3
  • 34
  • 68
0

You can rewrite how your onToggleLoop method is called from your render() method.

render() {
    var shuffleClassName = this.state.toggleActive ? "player-control-icon active" : "player-control-icon"

return (
  <div className="player-controls">
    <FontAwesome
      className="player-control-icon"
      name='refresh'
      onClick={(event) => this.onToggleLoop(event)}
      spin={this.state.loopActive}
    />       
  </div>
    );
  }

The React documentation shows this pattern in making calls to functions from expressions in attributes.

T. Webster
  • 8,411
  • 5
  • 62
  • 90