Reputation: 11095
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?
Upvotes: 223
Views: 175864
Reputation: 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)}
Upvotes: -1
Reputation: 11
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.
Upvotes: 1
Reputation: 10099
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.
Upvotes: 0
Reputation: 16908
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);
}
Upvotes: 284
Reputation: 4341
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} } />
})
Upvotes: 0
Reputation: 9720
in my case this was the solution = () => {}
methodName = (params) => {
//your code here with this.something
}
Upvotes: 4
Reputation: 1003
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
Upvotes: 3
Reputation: 3075
Write your function this way:
onToggleLoop = (event) => {
this.setState({loopActive: !this.state.loopActive})
this.props.onToggleLoop()
}
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)).
Upvotes: 37
Reputation: 101
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>
)
}
}
Upvotes: 1
Reputation: 1275
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.
Upvotes: 0
Reputation: 27574
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)} />
)}
Upvotes: 12
Reputation: 4945
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)}
.
Upvotes: 109