React: "this" is undefined inside a component function
Asked Answered
V

12

222
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?

Vitascope answered 28/11, 2015 at 16:30 Comment(0)
A
283

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);

}
Achromatize answered 28/11, 2015 at 16:40 Comment(10)
if you change your onClick property to () => this.onToggleLoop after moving the onToggleLoop function into your react class it will work as well.Duckworth
Do you really have to bind every method of every react class? Isn't that a little crazy?Hogfish
@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: babeljs.io/blog/2015/06/07/react-on-es6-plusAchromatize
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?Rodent
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 :(Coomer
@Achromatize the article is actually at: babeljs.io/blog/2015/07/07/react-on-es6-plus and thanks for that information it was exactly what I was looking for! :DAylsworth
TLDR for the article: use arrow functions instead.Weiser
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.Cordie
I found I had to bind my onClick property slightly differently to @Duckworth . In the example shown it would be onClick={ () => { this.onToggleLoop(); }} - in general im with the "Isn't this a little crazy" crowd.Erich
why is this undefined, for click event listener, this would refer to the element on which the lsitener is registered, so technically wouldnt it refer to a html element and not undefined?????Chromatolysis
F
109

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)}.

Frechette answered 28/11, 2015 at 16:38 Comment(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 ?Professionalize
from developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…; An arrow function does not create its own this, the this value of the enclosing execution context is used.Frechette
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 PureComponents.Carduaceous
G
37

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)).

Gomorrah answered 4/12, 2017 at 11:15 Comment(3)
If I do that I get ReferenceError: fields are not currently supported.Cordie
It works if inside the constructor I say this.func = () => { ... }, but I regard that as kind of goofy and want to avoid it if possible.Cordie
So terrible that you can't use normal class syntax in React!Copper
P
12

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)} />
)}
Postmaster answered 23/6, 2016 at 23:9 Comment(2)
That's a lot of unnecessary functions you're creating there, each and every time the list is rendered...Wychelm
@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 helpfulPostmaster
H
4

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

methodName = (params) => {
//your code here with this.something
}
Hoogh answered 19/12, 2019 at 14:22 Comment(1)
this is the real solution. Forgot to put it on the one function that was failing me ThanksFacture
P
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
Parian answered 5/9, 2019 at 18:9 Comment(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'*/Cassette
J
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>
    )
  }

}
Janise answered 14/3, 2019 at 10:11 Comment(0)
D
1

I want to give an explanation of why this is undefined:
If we use this in a function that is not an arrow function, this is bound to a global object when not in strict mode. But with strict mode, this will be undefined (https://www.w3schools.com/js/js_this.asp).

And ES6 modules are always in strict mode (javascript: use strict is unnecessary inside of modules).

You can bind this in onToggleLoop function with the instance of PlayerControls component by using bind method inside the constructor:

constructor(props) {
    super(props)

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

    this.onToggleLoop = this.onToggleLoop.bind(this)
}

Or use the arrow function instead:

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

The arrow function does not have context, so this in the arrow function will represent the object that defined the arrow function.

Diptych answered 23/7, 2022 at 15:49 Comment(1)
Add something about being able to bind this to provide context. The concept of this in JS can be very confusing and generally requires a lot more explication.Pyromania
P
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.

Professionalize answered 15/9, 2017 at 7:41 Comment(0)
R
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} } />
})
Radioscopy answered 15/1, 2020 at 6:55 Comment(0)
I
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.

Iceni answered 16/10, 2020 at 14:12 Comment(0)
A
-1

I recently ran into "this is undefined" error The method below worked for me. I passed the state to my class constructor and had to simply pass the function being used as an arrow function to my listener.

const cartUtils: CartUtils = new CartUtils(cartItems, setCartItems);

onClick={(evt) => cartUtils.getProductInfo(evt)}
Apocalyptic answered 4/4, 2023 at 23:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.