Alex Yong
Alex Yong

Reputation: 7645

onClick handler is triggered on each render cycle

I have default state like this

this.state = {
  selectedTab : 'tab1'
}

then

My render method like this

render(){
   const { selectedTab } = this.state;
   return(
      <li>tab1</li><li>tab2</li>
      <div id="content">
         {selectedTab == 'tab1' ? this.renderTab1Content() : this.renderTab2Content()}
      </div>
   )
}

Everything worked above. But I failed to implement switch tab using click event.

I tried to bind onclick event on my li to change the state of selectedTab, but I got infinity loop error. Like this

<li onClick={this.setState({selectedTab :'tab1'})}>Tab 1</li>
<li onClick={this.setState({selectedTab :'someothertab'})}>Tab 2/li>

Why?

Upvotes: 1

Views: 1942

Answers (4)

abhishek kasana
abhishek kasana

Reputation: 1522

Whenever setState is called it causes render method to be run, and when u assign a function call instead of a function refrence to some variable, it calls it instantly.

In your case your function refrence, setState is called instantly on assignment and further setState calls render again, which further causes the setState call -> setState render, this creates an endless loop.

<li onClick={this.setState({selectedTab :'tab1'})}>Tab 1</li>

Render -> onclick => setState -> Render -> onclick => setState -> ....

To overcome this there are two ways,

You can use arrow functions, which also helps you to bind this reference of class to the function.

onClick={() => (this.setState())}

or if using simple function then you can bind them in the constructor.

constructor(props){
super(props);
this.onClickMethod = this.onClickMethod.bind(this);
// if this is not done then this in onclick method would not refer to the class 
// instead it would that of assigned onClick callback as it is called 
// by onclick Event handler hence exectuted in different context.
}

onClickMethod() {
 this.setSate();
}

<li onClick={this.onClickMethod}>Tab 1</li>

Upvotes: 0

Andreyco
Andreyco

Reputation: 22872

I will introduce concept of "higher order functions here".

Basicaly, higher order function is function returning another function.

Let's modify onClick handler and make it "higher order function".

// Original function
setActiveTab = (activeTab) => {
  this.setState({ activeTab });
}

// Higher order function
setActiveTab = (activeTab) => {
  // Here, we already "remember" name of tab which becomes active after click.
  // Return "true" `onClick` handler from this place.
  return () => {
    // Finaly set state after click.
    this.setState({ activeTab });
  }
}

How will render function look like?

<li onClick={this.setActiveTab('tab1')}>Tab 1</li>
<li onClick={this.setActiveTab('someothertab')}>Tab 2/li>

Better, isn't it?

Upvotes: 1

Shubham Khatri
Shubham Khatri

Reputation: 282080

This error happens because you onClick handler expects a function but you have called a statement to setState on the event and hence everytime you state changes using setState, the render is called again and hence the onClick again calls the setState which triggers an infinite loop. You can do this by using an arrow function in onClick event or calling a separate function

<li onClick={() => this.setState({selectedTab :'tab1'})}>Tab 1</li>

or

handleClick = () =>{
    this.setState({selectedTab :'tab1'})
}


<li onClick={this.handleClick}>Tab 1</li>

Upvotes: 2

Panther
Panther

Reputation: 9418

Every time your li gets rendered, the setState is automatically called and hence you get into a loop. You should do something like this.

Create a method in the component like this

setActiveTab = (tab) => {
 this.setState({selectedTab : tab});
}

Then in your render method rewrite your li like this

<li onClick={() => this.setActiveTab('tab1')}>Tab 1</li>
<li onClick={() => this.setActiveTab('someothertab')}>Tab 2/li>

Upvotes: 0

Related Questions