Null Salad
Null Salad

Reputation: 1030

React successfully executes a function onClick but not in other parts of this.render

I have a function stopRecording() that I'd like to be called when a timer runs out, or when someone presses a stop button. The problem is that when it is called when the timer runs out (the first half of the render function) it is called continuously, despite me bracketing it in an if clause. When it is called as a button event (in the return half of the render function) then it works fine.

Note my console logs. When I open the console in Chrome and let the timer run out, the console logs I marked as successful in my code body runs, but NOT ones that I commented with //!!!. I also get the following error continuously: Invariant Violation: setState(...): Cannot update during an existing state transition (such as withinrender). Render methods should be a pure function of props and state.

//...
stopRecording: function() {
    if (this.state.recording){
        console.log("this will log as expected") 
        this.setState({recordingStatus:"",
                        recording:false})
        console.log("this will NOT log as expected'") //!!!
        this.props.emit("recEmit")
    }
}
render: function() {
    var timeBar;
    var countdown = "0";
    var timeBarFill = "#FF9090"
    if (this.state.recording){
        countdown = new Date()-this.state.startTime
        timeBarFill = "#FF3830";
        if (countdown > this.state.maxRecLength){
            console.log('this will log as expected')
            countdown=0
            this.stopRecording()
            console.log('this will NOT log as expected') //!!!

        };
    }
//...
return(
    //...
     <button type="button" id="button" onClick={this.stopRecording}><b>Stop</b></button>
    //...
)

Upvotes: 0

Views: 278

Answers (2)

xyc
xyc

Reputation: 181

You should never call setState inside render(): https://github.com/facebook/react/issues/5591#issuecomment-161678219

As render should be a pure function of the component's props and state, which means that it should not have any side effects (like changing its own state).

Also, you can't guarantee that React will call your component's render() method when your countdown is about to expire. Consider using setTimeout in component's life cycle methods.

Upvotes: 1

Nicola Pedretti
Nicola Pedretti

Reputation: 5166

I think that this is due to how states work in react. This article explains it pretty well. I suggest to read it but I can some it up for you:

setState is usually called asynchronously.

if setState is not triggered by an event that React can keep track of, such as onClick, it is called synchronously.

This means that when you are using onClick everything goes fine because your call of setState in stopRecording does not block and the function finishes before a re render is called. When a timer triggers it this happens synchronously, the state changes and render is called again.

Now, I still do not understand how it can run continuously, since it should have set the state.recording variable to false and I don't see anything that turns it back to true.

Also, be careful to use states just for variables that are truly states: change with time. The maxRecordinLength does not seem to be a state variable, and same for startTime.

EDIT:

after I saw the update I realized that the main issue here is changing a state inside of the render method. I posted this link in a comment here but I think it is worth explaining.

Basically, you can solve your issue by calling a setTimer function in the componentDidMount function of react-- more on this here. Something like:

componentDidMount: function(){
        setTimer(this.myFunction, this.props.maxRecLength);
},

And you myFunction would look like this:

myFunction: function(){
        this.setState({timeElapsed: true});
 },

Then you can use this.state.timeElapsed in your render function, and whatever is in there will be displayed after the maxRecLength is reached.

Upvotes: 0

Related Questions