Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cannot update during an existing state transition error in React

Inside my render return() I have these:

<button onClick={ this.selectTimeframe('Today') }
        className={ this.setActiveIf('Today') } type="button">Today</button>

Which is this function:

selectTimeframe(timeframe) {
    // this.setState({ timeframe });
}

^ I have to comment out the setState for now otherwise I'll get that error I posted above and the app breaks.

I have this in my constructor:

this.selectTimeframe = this.selectTimeframe.bind(this);

I found this answer here, but it does not make sense, how am I suppose to pass in variable? Or is he saying that every unique button needs a unique function? As to avoid calling it inside of the render?

Full code

import React from 'react';

export class TimeframeFilter extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            timeframe: 'Today'
        };

        this.selectTimeframe = this.selectTimeframe.bind(this);
        this.setActiveIf = this.setActiveIf.bind(this);
    }

    componentDidMount() {
        console.log('%c TimeframeFilter componentDidMount', 'background: #393939; color: #bada55', this.state);
    }

    selectTimeframe(timeframe) {
        // this.setState({ timeframe });
    }

    setActiveIf(timeframe) {
        return this.state.timeframe === timeframe ? 'btn btn-default active' : 'btn btn-default';
    }

    render() {
        return (
            <div className="fl">
                <span className="label label-default mr10">Timeframe</span>
                <div className="btn-group btn-group-sm mr20" role="group">
                    <button onClick={ this.selectTimeframe('Today') }
                            className={ this.setActiveIf('Today') } type="button">Today</button>
                    <button onClick={ this.selectTimeframe('Week') }
                            className={ this.setActiveIf('Week') } type="button">Week</button>
                    <button onClick={ this.selectTimeframe('Month') }
                            className={ this.setActiveIf('Month') } type="button">Month</button>
                    <button onClick={ this.selectTimeframe('Year') }
                            className={ this.setActiveIf('Year') } type="button">Year</button>
                    <button onClick={ this.selectTimeframe('All') }
                            className={ this.setActiveIf('All') } type="button">All</button>
                </div>
            </div>
        );
    }
}

export default TimeframeFilter;
like image 374
Leon Gaban Avatar asked May 16 '17 19:05

Leon Gaban


People also ask

Why is React not updating on state change?

State updates in React are asynchronous; when an update is requested, there is no guarantee that the updates will be made immediately. The updater functions enqueue changes to the component state, but React may delay the changes, updating several components in a single pass.

How do I update my state data in React?

To update our state, we use this. setState() and pass in an object. This object will get merged with the current state.

Can I update state in render React?

Whenever we update the state using the setState() method it re-renders the current component and its child components. When we call the setState function it receives the latest state snapshot. Then we can use it inside the setState() function and return the new value to which we want to update the new state value to.

Can we update state in componentDidMount?

You may call setState() immediately in componentDidMount() . It will trigger an extra rendering, but it will happen before the browser updates the screen. This guarantees that even though the render() will be called twice in this case, the user won't see the intermediate state.


2 Answers

You should be passing the value to the onClick function using arrow function or bind method otherwise it just executes to return value to onClick everytime rerender occurs and if you use setState in it, it will call a rerender and the funciton to call again and thus repeating the process.

Do it like

<button onClick={ ()=> this.selectTimeframe('Today') }
        className={ this.setActiveIf('Today') } type="button">Today</button>

or

<button onClick={ this.selectTimeframe.bind(this, 'Today') }
        className={ this.setActiveIf('Today') } type="button">Today</button>

Hope I was able to explain it to you clearly. :)

EDIT: Although the above approach solves the problem but isn't the optimal approach due to the performance reasons. We should avoid method binding inside render because during re-rendering it will create the new methods instead of using the old one, that will affect the performance.

See this answer on How to avoid binding inside the render method

like image 125
Shubham Khatri Avatar answered Oct 26 '22 23:10

Shubham Khatri


Currently you call this.selectTimeframe('Today') in your render method. You probably want to

onClick={() => this.selectTimeframe('Today') }

or a bit smarter way

<button onClick={ this.selectTimeframe.bind(this, 'Today') } className={ this.setActiveIf('Today') } type="button">Today</button>

selectTimeframe.bind(key) {
  switch(key){
    case 'Today':
      //your logic
      break;
  }
}
like image 1
kurumkan Avatar answered Oct 27 '22 00:10

kurumkan