When a button is clicked {this.increment} is invoked. Why {this.increment} runs without binding {this.increment.bind(this)}
export default class App extends Component {
constructor(props) {
super(props)
this.state = {
count: 0
}
}
increment=()=>{
this.setState({ count: this.state.count + 2 })
}
render() {
return (
<>
<div id='1'>{this.state.count}</div>
<button onClick={this.increment}>Increment </button>
</>
)
}
}
I think you are confused about why binding is required. It is not required because the function will not be able to run without it. You are passing an event handler to the onClick and it will run when button is clicked.
When this piece of code is run : this.increment, the this is an instance of the class and hence the component. But the assignment: onClick={this.increment}, does something like the below:
class Animal {
constructor(name) {
this.name = name;
}
speak() { console.log(`${JSON.stringify(this)}`);
}
}
let meth = ani.speak;
meth();
The context is lost. And this becomes undefined. So even if increment runs, this.setState will throw an error. And that is why binding is required.
In the constructor add this statement:
this.increment = this.increment.bind(this)
Now your increment is assigned a new function(a new value), where the value of this (context) is the class instance.
Related
Question: I want to press the "Manipulator" class button to change the ChangeValue's value variable to Manipulator's manipulatorName value, which isn't occurring. What have I done wrong?
I have a class (called ChangeValue) that initialises with an empty string name. I'm doing this since it will be displayed on the website as "Empty" for now. However, when I click a another class (called Manipulator) it should change ChangeValue, which isn't occurring. The ChangeValue's value is always set to "Empty", despite when I click the button.
My code:
export class ChangeValue extends React.Component {
constructor(props) {
super(props)
this.state = {
value: " "
};
}
render() {
var currentValue = null;
if (this.value == null) {
currentValue = "Empty"
} else {
currentValue = this.value
}
return (
currentValue
)
}
}
export class Manipulator extends React.Component {
constructor(props) {
super(props)
this.state = {
manipulatorName: "New Value"
};
this.handleClick = this.handleClick.bind(this);
}
render() {
return (
<button onClick = {this.handleClick}>
<ChangeValue value = {this.state.manipulatorName} />
</button>
)
}
}
I based the "ChangeValue value" line based off what I was reading from Stack and it may be there is an issue with Parent/Children, too?
There are a few things going on here as to why it's not working.
You can clean up your ChangeValue component, as it doesn't actually use any state. It only needs to use the value of the prop passed to it from the Manipulator, so therefore can be converted into a stateless 'dumb' function based component.
function ChangeValue(props) {
return props.value || "Empty"
}
Or if you still want it as a class so you can add some state in later...
export class ChangeValue extends React.Component {
render() {
return this.props.value || "Empty"
}
}
These will both have the same output. They will return the value of props.value if it is truthy, OR (||) return the word "Empty" if props.value is falsy.
The Manipulator class needs a little bit of work also. It currently sets up a handleClick method but doesn't define it. It should probably look something like this...
export class Manipulator extends React.Component {
constructor(props) {
super(props);
this.state = {
manipulatorName: undefined
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({
manipulatorName: "New Value After Click"
});
}
render() {
return (
<button onClick={this.handleClick}>
<ChangeValue value={this.state.manipulatorName} />
</button>
);
}
}
This will initially render a button with the text "Empty" as the manipulatorName is *falsy*. Upon clicking the button, the button text should then change to say "New Value After Click".
I hope this helps, if it doesn't quite fit with what you're trying to achieve, please comment or update the question with some further details.
UPDATE
Here is a working example on CodeSandbox.
Essentially, I want to invoke a callback passed down from a parent component then reassign a value. I've tried creating a class method that invokes the given callback function from the parent components props, but I'm getting an infinite loop with setState. On another method that I tried but cannot seem to replicate at the moment, an error was thrown that stated "callback is not a function".
Perhaps, I'm phrasing this in a weird way. Here's an example:
class ParentComponent extends React.Component {
constructor(props) {
super(props);
this.state = { parentState: true }
this._handleToggle = this._handleToggle.bind(this);
}
_handleToggle() {
this.setState({ parentState: !this.state.parentState })
}
render() {
return (
<ChildComponent
onSomeEvent={this._handleToggle}
/>
)
}
}
class ChildComponent extends React.Component {
constructor(props) {
super(props);
this.randomInteger = 8;
this._invokeCallback = this._invokeCallback.bind(this);
}
// this is where I'm having trouble
_invokeCallback(callback) {
callback();
this.randomInteger = 0;
}
render() {
const { onSomeEvent } = this.props;
// Error
return (
<button onClick={this._invokeCallback(onSomeEvent)} />
)
}
}
What I want from here is to reassign this.randomInteger to a certain value AFTER invoking the callback that was handed down from the parent component. What can I do from here?
I apologize if my example is missing some pieces or is incomplete. I am rushing to write this up. Please feel free to correct any mistakes I made in my phrasing or example code. Thanks in advance.
Your _invokeCallback is executing immediately.
Due to the parentheses and passing an argument here this._invokeCallback(onSomeEvent), you are setting onClick to the result of the _invokeCallback method.
This is what is causing the infinite loop where setState in the parent causes a re-render in the child which then executes _invokeCallback again, and so on.
You could use an anonymous function with onClick so that _invokeCallback is only executed when the button is clicked:
render() {
const { onSomeEvent } = this.props
return (
<button onClick={ () => this._invokeCallback(onSomeEvent) } />
)
}
Alternatively, you could call the onSomeEvent function from the _invokeCallback method:
_invokeCallback() {
this.props.onSomeEvent()
this.randomInteger = 0
}
render() {
return (
<button onClick={ this._invokeCallback } />
)
}
I'm trying to follow the no-bind rule for React using the pattern that they have recommended with ES6 classes:
class Foo extends React.Component {
constructor() {
super();
this._onClick = this._onClick.bind(this);
}
render() {
return (
<div onClick={this._onClick}>
Hello!
</div>
);
}
_onClick() {
// Do whatever you like, referencing "this" as appropriate
}
}
However, when I need to pass arguments in to _onClick, what needs to change?
I've tried something like:
import {someFunc} from 'some/path';
class Foo extends React.Component {
constructor() {
super();
this._onClick = this._onClick.bind(this, a, b);
}
render() {
const {
prop1,
prop2
} = this.props;
return (
<div onClick={this._onClick(prop1, prop2}>
Hello!
</div>
);
}
_onClick = (a, b) => {
someFunc(a, b);
}
}
However, this does not work. What needs to be altered?
The call to bind in the constructor should only pass this as a single argument.
this._onClick = this._onClick.bind(this);
Here you are overwriting the property this._onClick with a new one that has the correct this bound. If your function takes two arguments, then you should pass those as normal at call time.
Passing additional arguments to bind means that the function returned already has those arguments supplied - in your attempt the _onClick function will always have its first two arguments undefined, as a and b have no value in the constructor.
Now that you have bound this to your function, you can access this.props from within there, rather than having to pass arguments:
import {someFunc} from 'some/path';
class Foo extends React.Component {
constructor() {
super();
this._onClick = this._onClick.bind(this);
}
render() {
return (
<div onClick={this._onClick}>
Hello!
</div>
);
}
_onClick() {
const {
prop1,
prop2
} = this.props;
someFunc(prop1, prop2);
}
}
You should use partial application. Basically you initialise your onClick function with the parameters you want, and the onClick function will return a new function to be called when the div is clicked.
import {someFunc} from 'some/path';
class Foo extends React.Component {
render() {
return (
<div onClick={this._onClick(prop1, prop2)}>
Hello!
</div>
);
}
_onClick = (a, b) => {
return () => someFunc(a, b);
}
}
PS: this only applies if your parameters a and b are not part of your this.props, if they are then you should just do as Tom Fenech said.
To answer your question there is nothing special you have to do in order to pass arguments to your this._onClick function.
the proper revised code will be:
class Foo extends React.Component {
constructor() {
super();
this._onClick = this._onClick.bind(this);
}
render() {
return (
<div onClick={() => this._onClick(1, 2)}>
Hello!
</div>
);
}
_onClick = (a, b) => {
console.log(a, b);
}
}
Secondly, the way you are calling this._onClick is not the right way to invoke a function on click.
Right now what is happening that on each render process your function is getting called because you didn't pass the function as an argument rather you invoked that function and assigned its returned value to the onClick prop.
you have to do this like:
render() {
return (
<div onClick={() => this._onClick(prop1, prop2)}>
Hello!
</div>
);
}
By Invoking your function this way you ensure the this._onClick will get called when click event occurs.
Another method is to use Babel stage 1 autobind and skip the constructor.
import {someFunc} from 'some/path';
class Foo extends React.Component {
_onClick = () => {
const {
prop1,
prop2
} = this.props;
someFunc(prop1, prop2);
}
render() {
return (
<div onClick={this._onClick}>
Hello!
</div>
);
}
}
I'm building my first React app and am seemingly way over my head. Anyway, I'm trying to take a component like this:
export default class Timer extends Component {
constructor(props) {
super(props)
this.state = { clock: 0, time: '' }
}
componentDidMount() {
this.play()
}
componentWillUnmount() {
this.pause()
}
pause() {
if (interval) {
clearInterval(interval)
interval = null
}
}
render() {
return (
<div className="react-timer" pause={this.pause.bind(this)}>
<h3 className="seconds"> {this.state.time} {this.props.prefix}</h3>
<br />
</div>
)
}
}
Timer.propTypes = {
options: PropTypes.object
}
and access it's state and pause functions in another component that is it's parent because the timer is embedded in the other component.
Other component:
class Level1 extends Component {
constructor(props) {
super(props);
this.state = {x: 0, y: 0};
}
render () {
...
return (
<div>
<div ref="elem" onMouseMove={this._onMouseMove.bind(this)} id="gameBoard">
<img id="waldo1" src={require('../images/waldo1(1).jpg')} alt="waldo"/>
<h2> {x} , {y}</h2>
</div>
<button onClick={this.refs.mytimer.pause()}>Pause</button>
<Timer ref="mytimer" options={OPTIONS}/> <-- Here Timer
</div>
) // return
} // render
} //component
For example, I'm going to write a function like this:
var isWaldoFound = function (x , y ) {
if (true) {
Timer.pause()
hashHistory.push({ '/result' + this.Timer.state})
} else {
..Whatever..
}
}
I've tried using refs and props but when I log anything it's undefined. When I log timer it shows me the Timer but when I log Timer.pause or Timer.pause() it says it is undefined.
How can I go about doing this?
React is all about your UI being a function of your state; i.e., React is all about state. So, instead of doing things imperatively, you change your state and the components "react" to the change in state.
You might want to read:
https://zhenyong.github.io/react/docs/more-about-refs.html
and
https://facebook.github.io/react/docs/refs-and-the-dom.html
In your case, you might pass the "current state" as a prop to the Timer, and then in componentWillRecieveProps() in the Timer, check the new "current state" against the current one, and if there's a change in state, then have the timer transition itself to the new "current state", rather than trying to imperatively tell the timer to transition to a new state.
So, what you're trying to do is not really the "React" way, but you should still be able to make it work...
First, I'd recommend using a callback ref instead of a string ref, and ideally the callback is a class method on the outer component instead of a fat-arrow function so that a new function isn't generated with each render. Use the ref to capture the Timer instance on first render, store that instance as an instance variable of your Level1 component, and then use the instance later when the pause button is clicked.
Something like this maybe (untested of course):
class Level1 extends Component {
constructor(props) {
super(props);
this.captureTimer.bind(this)
this.onPauseTimerButtonClicked.bind(this)
this.state = {x: 0, y: 0};
}
captureTimer(ref) {
this.timer = ref
}
onPauseTimerButtonClicked() {
this.timer.pause()
}
render () {
...
return (
<div>
<div ref="elem" onMouseMove={this._onMouseMove.bind(this)} id="gameBoard">
<img id="waldo1" src={require('../images/waldo1(1).jpg')} alt="waldo"/>
<h2> {x} , {y}</h2>
</div>
<button onClick={this.onPauseTimerButtonClicked}>Pause</button>
<Timer ref={timer => this.captureTimer(timer)} options={OPTIONS}/> <-- Here Timer
</div>
) // return
} // render
} //component
The reason it's not working is probably because the pause function is not bound to the class.
Try binding it in the constructor of your component:
constructor(props) {
super(props)
this.state = { clock: 0, time: '' }
this.pause = this.pause.bind(this);
}
Try this pen: https://codepen.io/CarlosEME/pen/xgmYXZ
Assuming isWaldoFound is a method of Level1.. If so , replace :
Timer.pause()
By :
this.refs.mytimer.pause();
I have one parent component and child component, which the parent include the child one. They have the same counter state:
import React from 'react';
class Child extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 100
}
}
render = () => {
return (
<div>
<h2>{this.state.counter}</h2>
<button onClick={this.props.clickHandler.bind(this)}>Click</button>
</div>
)
}
}
export default class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 0
}
}
addCounter = () => {
this.setState({
counter: ++this.state.counter
})
}
render = () => {
return (
<div>
<h1>{this.state.counter}</h1>
<Child clickHandler={this.addCounter} />
</div>
)
}
}
I pass the addCounter handler from parent to the child one, and I think the context of the addCounter could change from parent to child through bind method, then when I click the button, the counter in child would increased instead of the parent counter state.
But the fact is, no matter the clickHandler bind null or this, the function only increase the parent's state counter.
So what's wrong with my thought, why the context doesn't change?
It is because you are using arrow functions. It is one of the purposes of arrow functions to maintain the "this" of the parent. This should explain it.
Change:
addCounter = () => { //your code}
To:
addCounter () {//your code}
Because you have defined addCounter via the ES6 arrow function syntax, this is already implicitly bound to the parent object. You can bind the function again but this has no effect, as the binding is already in place.