Binding event handlers in React Components fires onClick when rendered - reactjs

I am learning React. In a test app I'm writing, I am rendering some buttons with onClick methods. When they are rendered like this, they work and call the selectMode function as expected when clicked.
constructor(props) {
super(props);
this.state = { mode: 'commits', commits: [], forks: [], pulls: [] };
}
...
selectMode(mode) {
this.setState({ mode });
}
render() {
...
return (<div>
<button onClick={this.selectMode.bind(this, 'commits')}>Show Commits</button><br/>
<button onClick={this.selectMode.bind(this, 'forks')}>Show Forks</button><br/>
<button onClick={this.selectMode.bind(this, 'pulls')}>Show Pulls</button>
</div>
)
}
But when I tried the suggested best practices way shown below, by binding in the constructor, the selectMode function is called three times when the component is rendered. Why are the onClick event handlers being called then? What do I have wrong?
constructor(props) {
super(props);
this.state = { mode: 'commits', commits: [], forks: [], pulls: [] };
this.selectMode = this.selectMode.bind(this)
}
...
selectMode(mode) {
this.setState({ mode });
}
render() {
...
return (<div>
<button onClick={this.selectMode('commits')}>Show Commits</button><br/>
<button onClick={this.selectMode('forks')}>Show Forks</button><br/>
<button onClick={this.selectMode('pulls')}>Show Pulls</button>
</div>
)
}

your this.selectMode(...) is executed IMMEDIATELY whenever your component is rendered.
<.. onClick={this.selectMode('commits')}..../> <-- function is called immediately
You can use arrow function to create an anonymous function in which you can call your method. In this way, you this.selectMode method only get called when the click event occurs :
<.. onClick={() => this.selectMode('commits')}..../>
If you don't want to create anonymous functions everytime you render the component, you can store an value to an attribute of the element. Like this :
constructor(props) {
super(props);
this.state = { mode: 'commits', commits: [], forks: [], pulls: [] };
this.selectMode = this.selectMode.bind(this)
}
selectMode(event){
this.setState({mode: e.target.name});
}
render(){
....
<.. onClick={this.selectMode} name='commits' ..../>
..}

I'm not sure, but I think it's because you call upon each onClick function by adding the parentheses. If you use ES6 you could try doing this:
onClick = () => { this.selectMode('commits') }
onClick = () => { this.selectMode('forks') }
onClick = () => { this.selectMode('pulls') }

Related

getting error: Cannot read property state of undefined

import React, { Component } from "react";
import FormUpdate from "../components/formUpdate";
import { fetchClothingItem, updateClothingItem } from "../actions/crud";
export default class Update extends Component {
constructor(props) {
super(props);
this.state = {
updateClothingItem: {}
};
}
componentWillMount() {
fetchClothingItem(this.props.match.params.postId)
.then(data => {
this.setState(state => {
state.updateClothingItem = data;
return state;
});
console.log("data", data);
//HERE IT IS RETURNING EXPECTED DATA
console.log("this.state.updateClothingItem",this.state.updateClothingItem)
})
.catch(err => {
console.error("err", err);
});
}
handleSubmit(data) {
//HERE IT IS THROWING:
> "TypeError: Cannot read property 'state' of undefined"
console.log("this.state.updateClothingItem", this.state.updateClothingItem);
updateClothingItem(this.state.updateClothingItem.id, data); this.props.router.push("/update");
}
render() {
return (
<div>
<FormUpdate
//onSubmit={this.handleSubmit.bind(this)}
id={this.state.updateClothingItem.id}
name={this.state.updateClothingItem.name}
sleeveLength={this.state.updateClothingItem.sleeveLength}
fabricWeight={this.state.updateClothingItem.fabricWeight}
mood={this.state.updateClothingItem.body}
color={this.state.updateClothingItem.color}
/>
<button
type="submit"
onClick={this.handleSubmit}
className="addItemButton"
>
Button
</button>
</div>
);
}
}
There are a few things that are technically wrong in terms of React code implementation.
Firstly, With ES6 style of writing a class, any function that needs to access the Class properties need to be explicitly binded. In your case you need to bind the handleSubmit function using arrow function of or binding in constructor.
See this answer for more details: Why and when do we need to bind functions and eventHandlers in React?
Secondly: You have your async request set up in the componentWillMount function and in the success response of it, you are setting state. However using setState in componentWillMount is triggered after the component is rendered so you still need to have an undefined check. You should instead make use of componentDidMount lifecycle function for async requests.
Check this answer on whether to have AJAX request in componentDidMount or componentWillMount
Third: setState is asynchronous and hence logging the state values after the setState function won't result in the correct output being displayed. Use the setState callback instead.
See these answers for more details:
calling setState doesn't mutate state immediately
When to use React setState callback
Code:
export default class Update extends Component {
constructor(props) {
super(props);
this.state = {
updateClothingItem: {}
};
}
componentDidMount() {
fetchClothingItem(this.props.match.params.postId)
.then(data => {
this.setState(state => {
state.updateClothingItem = data;
return state;
});
console.log("data", data);
//HERE IT IS RETURNING EXPECTED DATA
console.log("this.state.updateClothingItem",this.state.updateClothingItem)
}) // this statement will not show you correct result since setState is async
.catch(err => {
console.error("err", err);
});
}
handleSubmit = (data) => { . // binding using arrow function here
console.log("this.state.updateClothingItem", this.state.updateClothingItem);
updateClothingItem(this.state.updateClothingItem.id, data); this.props.router.push("/update");
}
render() {
return (
<div>
<FormUpdate
//onSubmit={this.handleSubmit.bind(this)}
id={this.state.updateClothingItem.id}
name={this.state.updateClothingItem.name}
sleeveLength={this.state.updateClothingItem.sleeveLength}
fabricWeight={this.state.updateClothingItem.fabricWeight}
mood={this.state.updateClothingItem.body}
color={this.state.updateClothingItem.color}
/>
<button
type="submit"
onClick={this.handleSubmit}
className="addItemButton"
>
Button
</button>
</div>
);
}
}
You forgot to bind your handleSubmit function to the class. You can either use arrow function to define the function.
handleSubmit=(data) =>{
...
}
Or you can bind the function in your constructor.
constructor(props) {
super(props);
this.state = {
updateClothingItem: {}
};
this.handleSubmit= this.handleSubmit.bind(this,data);
}
there is no state in constructor yet
if you want to set state in constructor you can do it like this
class SomeComponent extends Component {
constructor(props){
super(props)
this.state = { someKey: someValue }
}
}
or even like this
class SomeComponent extends Component {
state = { someKey: someValue }
}
but in this case babel should be properly configured

How to onclick method set for only one input?

First please click for SS.
Right now I have 2 input which has value credit-card and paypal.
I set an onClick event for CreditCard to provide card informations.It works fine but problem is:
Card details doesn`t disappear when I click paypal input. It works just if I click CreditCart input again. I want to make it disappear even I click paypal input. I mean card details should seem only by clicking Credit Card input.
class CreditCart extends React.Component {
constructor(props) {
super(props);
this.state = {show:false};
this.handleClick = this.handleClick.bind(this);
}
handleClick () {
this.setState({ show : !this.state.show})
}
render () {
return (
//2 input here credir-cart has onClick
{this.state.show && <CreditCart/>
}
Second component which includes cart information part:
class CreditCart extends React.Component {
constructor(props) {
super(props);
}
render () {
// information part
}
Your handleClick method is wrong. The setState method is asynchronous and it will try to execute it in batches which means that the previous value might not be updated yet.
Change it to
handleClick() {
this.setState(function (prevState, props) {
return {
show: !prevState.show
};
});
}
See State Updates May Be Asynchronous
I think something like the following should work for you...
class SomeComponent extends Component {
constructor() {
this.state = {
toggle: false
};
}
render() {
return (
<div>
{this.state.toggle
? <CreditCardComponent />
: <PaypalComponent />}
<button
onClick={e => this.setState({ toggle: !this.state.toggle })}
>
Toggle
</button>
</div>
);
}
}

React child component can't get props.object

My parent component is like this:
export default class MobileCompo extends React.Component {
constructor(props) {
super(props);
this.state = {
data: null,
datasets: {}
};
this.get_data = this.get_data.bind(this);
}
componentWillMount() {
this.get_data();
}
async get_data() {
const ret = post_api_and_return_data();
const content={};
ret.result.gsm.forEach((val, index) => {
content[val.city].push()
});
this.setState({data: ret.result.gsm, datasets: content});
}
render() {
console.log(this.state)
// I can see the value of `datasets` object
return (
<div>
<TableElement dict={d} content={this.state.data} />
<BubbleGraph maindata={this.state.datasets} labels="something"/>
</div>
)
}
}
child component:
export default class BubbleGraph extends React.Component {
constructor(props) {
super(props);
this.state = {
finalData: {datasets: []}
};
console.log(this.props);
// here I can't get this.props.maindata,it's always null,but I can get labels.It's confusing me!
}
componentWillMount() {
sortDict(this.props.maindata).forEach((val, index) => {
let tmpModel = {
label: '',
data: null
};
this.state.finalData.datasets.push(tmpModel)
});
}
render() {
return (
<div>
<h2>{this.props.labels}</h2>
<Bubble data={this.state.finalData}/>
</div>
);
}
}
I tried many times,but still don't work,I thought the reason is about await/async,but TableElement works well,also BubbleGraph can get labels.
I also tried to give a constant to datasets but the child component still can't get it.And I used this:
this.setState({ datasets: a});
BubbleGraph works.So I can't set two states at async method?
It is weird,am I missing something?
Any help would be great appreciate!
Add componentWillReceiveProps inside child componenet, and check do you get data.
componentWillReceiveProps(newProps)
{
console.log(newProps.maindata)
}
If yes, the reason is constructor methos is called only one time. On next setState on parent component,componentWillReceiveProps () method of child component receives new props. This method is not called on initial render.
Few Changes in Child component:
*As per DOC, Never mutate state variable directly by this.state.a='' or this.state.a.push(), always use setState to update the state values.
*use componentwillrecieveprops it will get called on whenever any change happen to props values, so you can avoid the asyn also, whenever you do the changes in state of parent component all the child component will get the updates values.
Use this child component:
export default class BubbleGraph extends React.Component {
constructor(props) {
super(props);
this.state = {
finalData: {datasets: []}
};
}
componentWillReceiveProps(newData) {
let data = sortDict(newData.maindata).map((val, index) => {
return {
label: '',
data: null
};
});
let finalData = JSON.parse(JSON.stringify(this.state.finalData));
finalData.datasets = finalData.datasets.concat(data);
this.setState({finalData});
}
render() {
return (
<div>
<h2>{this.props.labels}</h2>
<Bubble data={this.state.finalData}/>
</div>
);
}
}

Reactjs this.setState is not a function error

Im novice to React js, i don't know whats wrong with below code, but i'm getting setState is not a function error.Please help me to fix this.
class AppBarLayout extends React.Component {
constructor(props) {
super(props);
this.state = {
visibleSideBar:true,
slide:""
}
}
showProfile(){
this.setState({
slide:'slide'
});
console.log(this.state.slide);
}
render(){
return(
<div>
<header>
<NavBar show={this.showProfile}/>
<Profile slide={this.state.slide} />
</header>
</div>
);
}
}
export default AppBarLayout;
You need to bind this.showProfile in the component constructor
this.showProfile = this.showProfile.bind(this)
More detail about this on the Handling Events page of the React doc : https://facebook.github.io/react/docs/handling-events.html
Expanding on Delapouite's answer if you don't like to bind every function in the constructor you can use arrow functions to automatically bind to the correct context.
For example:
class AppBarLayout extends React.Component {
constructor(props) {
super(props);
this.state = {
visibleSideBar:true,
slide:""
}
}
// Now showProfile is an arrow function
showProfile = () => {
this.setState({
slide:'slide'
});
console.log(this.state.slide);
}
render(){
return(
<div>
<header>
<NavBar show={this.showProfile}/>
<Profile slide={this.state.slide}/>
</header>
</div>
);
}
}
export default AppBarLayout;
In my case, I solved the problem without binding.
Declaring the method like this was generating the error:
async onSubmit(e) {
event.preventDefault();
this.setState({ shopEthereumAddress: e.target.id });
}
The CORRECT declaration which will not generate the error is this:
onSubmit = async event => {
event.preventDefault();
this.setState({ shopEthereumAddress: event.target.id });
}
This works.
toggleSwitch() {
this.setState({
name: 'Ram ji'
});
}
Using an arrow function keeps the context of this set to the parent scope. The main benifit of arrow functions apart from being more concise is
Main benefit: No binding of ‘this’
// use this arrow function instead of
toggleSwitch = () => {
this.setState({
name: 'Ram ji' //It's working
});
}

React - Why is binding this not required in this example?

Trying to figure out the basics of React.
Looking at the second example on this page: https://facebook.github.io/react/
I see that the tick() function sets the state of the Timer class, incrementing the previous value by one.
class Timer extends React.Component {
constructor(props) {
super(props);
this.state = {secondsElapsed: 0};
}
tick() {
this.setState((prevState) => ({
secondsElapsed: prevState.secondsElapsed + 1
}));
}
componentDidMount() {
this.interval = setInterval(() => this.tick(), 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
return (
<div>Seconds Elapsed: {this.state.secondsElapsed}</div>
);
}
}
ReactDOM.render(<Timer />, mountNode);
However, when I tried to implement my own simple Counter class, it failed and I got a console error saying Cannot read property setState of undefined.
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {count: 0};
}
increment(prevState) {
this.setState((prevState) => ({
count: prevState.count + 1
}));
}
render() {
return (
<div className="main">
<button onClick={this.increment}>{this.state.count}</button>
</div>
)
}
}
Some Googling reveals that I have to bind this to the increment function. But why was that not required in the first example that I saw? I copied the code to CodePen and it ran fine with React 15.3.1 I cannot find anything resembling binding in that example. Only after I added binding code in the constructor did things start working in my example.
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {count: 0};
// THIS ONE LINE FIXED THE ISSUE
this.increment = this.increment.bind(this);
}
increment(prevState) {
this.setState((prevState) => ({
count: prevState.count + 1
}));
}
render() {
return (
<div className="main">
<button onClick={this.increment}>{this.state.count}</button>
</div>
)
}
}
Answering your question: the first example uses arrow functions, that automatically performs context binding. From the docs:
An arrow function does not create its own this context, so this has
its original meaning from the enclosing context.
Indeed there are some ways of binding in React:
1) you can bind all functions in your constructor, like you said:
constructor(props) {
/* ... */
this.increment = this.increment.bind(this);
}
2) invoke your callbacks with arrow functions:
<button onClick={(e) => this.increment(e)}>
3) append .bind at the end of your method reference each time you set it as a callback, like this:
<button onClick={this.increment.bind(this)}>
4) In your class, define the method with arrow functions:
increment = (e) => {
/* your class function defined as ES6 arrow function */
}
/* ... */
<button onClick={this.increment}>
In order to use this syntax with babel, you have to enable this plugin or use stage-2 preset.
If you look closely at the way the tick() function has been called in your fist example, you will understand that binding has been specified to it when it is called using the arrow functions. If you do the same for the increment function it will also work. These are just different ways of binding the functions.
So as asked, its not that in the first example no binding is specified while the second it requires, rather in both your cases you are binding just that the way of binding is different for both the cases.
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {count: 0};
}
increment(prevState) {
this.setState((prevState) => ({
count: prevState.count + 1
}));
}
render() {
return (
<div className="main">
<button onClick={() => this.increment()}>{this.state.count}</button>
</div>
)
}
}
ReactDOM.render(<Counter/>, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>

Resources