3

I am rewriting some old ReactJS code, and got stuck fixing this error (the error repeats about 1700 times in the console, the DOM does not render at all):

Warning: setState(...): Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to componentWillMount.

I am a Component that passes it's state down to a component that should render some controls. Based on the clicked controls, the state should change, and new controls should render.

So this is my Container component:

class TeaTimer extends Component {
    constructor(props) {
        super(props);
        this.state = {
            count: 120,
            countdownStatus: 'started'
        }
    }

    componentDidUpdate(prevProps, prevState) {
        if (this.state.countdownStatus !== prevState.countdownStatus) {
            switch (this.state.countdownStatus) {
                case 'started':
                    this.startTimer();
                    break;
                case 'stopped':
                    this.setState({count:0});
            }
        }
    }

    componentWillUnmount() {
        clearInterval(this.timer);
        delete this.timer;
    }

    startTimer() {
        this.timer = setInterval(() => {
            let newCount = this.state.count -1;
            this.setState({
                count: newCount >= 0 ? newCount : 0
            });
            if(newCount === 0) {
                this.setState({countdownStatus: 'stopped'});
            }
        }, 1000)
    }

    handleStatusChange(newStatus) {
        this.setState({ countdownStatus: newStatus });
    }

    render() {
        let {count, countdownStatus} = this.state;
        let renderStartStop = () => {
            if (countdownStatus !== 'stopped') {
                return <StartStop countdownStatus={countdownStatus} onStatusChange={this.handleStatusChange()}/>
            } else {
                return <div>This will be the slider form</div>
            }
        };
        return(
            <div className={styles.container}>
                <p>This is the TeaTimer component</p>
                <Clock totalSeconds={count}/>
                {renderStartStop()}
            </div>
        )
    }
}

And this is my controls component:

class StartStop extends Component {
    constructor(props) {
        super(props);
    }

    onStatusChange(newStatus) {
        return() => {
            this.props.onStatusChange(newStatus);
        }
    }

    render() {
        let {countdownStatus} = this.props;

        let renderStartStopButton = () => {
            if(countdownStatus === 'started') {
                return <button onClick={()=> this.onStatusChange('stopped')}>Reset</button>;
            } else {
                return <button onClick={()=> this.onStatusChange('started')}>Start</button>
            }
        };

        return(
            <div className={styles.tt.Controls}>
                {renderStartStopButton()}
            </div>
        )
    }
}

StartStop.propTypes = {
    countdownStatus: React.PropTypes.string.isRequired,
    onStatusChange: React.PropTypes.func.isRequired
};

I am sorry about the wall of text, but I really can;t figure out where the error is coming from - and therefor don't know which part of the code I can leave out.

I have tried implementing the solution found in a seemingly related question, but can't get it to work either.

Community
  • 1
  • 1
Miha Šušteršič
  • 9,742
  • 25
  • 92
  • 163

3 Answers3

8

I think you have a typo in this line:

return <StartStop countdownStatus={countdownStatus} onStatusChange={this.handleStatusChange()}/> 

It should be:

return <StartStop countdownStatus={countdownStatus} onStatusChange={() => this.handleStatusChange}/>

You seem to be calling the method handleStatusChange instead of passing it as a callback.

blurfus
  • 13,485
  • 8
  • 55
  • 61
Michał Grzejszczak
  • 2,599
  • 1
  • 23
  • 28
0

Your metods call each other so you must define two instance of your metods.

class StartStop extends Component {
    constructor(props) {
        super(props);
        this.onStatusChangeReset=this.onStatusChange.bind(this);
        this.onStatusChangeStart=this.onStatusChange.bind(this);

    }

    onStatusChange(newStatus) {
        return() => {
            this.props.onStatusChange(newStatus);
        }
    }

    render() {
        let {countdownStatus} = this.props;

        let renderStartStopButton = () => {
            if(countdownStatus === 'started') {
                return <button onClick={this.onStatusChangeReset('stopped')}>Reset</button>;
            } else {
                return <button onClick={this.onStatusChangeStart('started')}>Start</button>
            }
        };

        return(
            <div className={styles.tt.Controls}>
                {renderStartStopButton()}
            </div>
        )
    }
}

StartStop.propTypes = {
    countdownStatus: React.PropTypes.string.isRequired,
    onStatusChange: React.PropTypes.func.isRequired
};
Vahap Gencdal
  • 1,900
  • 18
  • 17
-1

In this line in your return <StartStop countdownStatus={countdownStatus} onStatusChange={this.handleStatusChange()}/> gives the warning, the handleStatusChanged function is called on pressing a button which tries to change the state by setState keyword. whenever the state is changed render function is called again but in your case render function was in progress of returning while the render function is called again by setState keyword.

Codesingh
  • 3,316
  • 1
  • 11
  • 18