13

I am trying to reset React state variables (to default values) in a container using setState() method. But getting the following error

 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`.

And finally: Maximum call stack size exceeded.

My code below:

resetMsg=()=>  {  
const company = this.state.company;
company.id = 0;
company.messages = [];    
this.setState({company: company});       
}

I am calling resetMsg() when variable in Redux state is true.

Code where I call resetMsg (The value of resetMessages is false initially and I need to reset React-state, when its true ):

    render() {
    if(this.props.resetMessages){           
        this.resetMsg();           
    }
user8125765
  • 209
  • 1
  • 2
  • 16
  • 5
    do not setstate in render method as it will rerender and it will be a infinite loop – Jeffin Jan 12 '18 at 12:27
  • ca you post where you call this resetMsg() ? – Jeffin Jan 12 '18 at 12:28
  • @Jeffin Any suggestion on how to reset values? – user8125765 Jan 12 '18 at 12:29
  • 2
    better call setState in componentWillMount or componentWillReceiveProps, not in render. you are creating an infinity loop by calling setState in render. because of the state change, the component renders again. you might want taking a look at the react lifecycle methods: https://reactjs.org/docs/react-component.html – bitwikinger Jan 12 '18 at 12:31

3 Answers3

20

You might want to look into componentWillReceiveProps(nextProps) function. As per the official docs:

componentWillReceiveProps() is invoked before a mounted component receives new props. If you need to update the state in response to prop changes (for example, to reset it), you may compare this.props and nextProps and perform state transitions using this.setState() in this method.

This is where you want to do the resets. So something like:

componentWillReceiveProps(nextProps) {
  if(nextProps.resetMessages) {
    const company = Object.assign({}, this.state.company);
    company.id = 0;
    company.messages = [];    
    this.setState({company: company});
  }
}

The snippet above will run every time props are sent down to the component. It first checks if the resetMessages prop is truthy. If it is, it will create a temporary copy of the company state, change the id and messages property values, and then update company with the new one.


I want to highlight the issues you had with your code:

  1. Calling setState() inside render() is a no-no.

    Whenever you call setState() in general the render() will be run afterwards. Doing so inside render() itself will cause that function to be called again, and again, and again...

  2. Mutating the state and/or props directly.

    This line const company = this.state.company; does not create a copy of the state variable. It only store the reference to it. So once you do this, and then do company.id = ... you are essentially doing this.state.company.id = ..., which is anti-pattern in React. We only ever change state through setState().

    To create a copy, use Object.assign({}, this.state.yourObject) for objects and this.state.yourArray.slice() for arrays.

Chris
  • 57,622
  • 19
  • 111
  • 137
  • @user8125765, you are welcome. Check the comments I added in my edit. – Chris Jan 12 '18 at 13:04
  • 1
    @Chris You have the method name misspelled as "componentWillRecieveProps" in your first sentence (though it is correct everywhere else). –  Jan 19 '19 at 02:55
13

componentWillReceiveProps is deprecated now (since June'18)

You should use one of the alternatives presented in the react docs instead.

In your case I guess it could be justified to use the 'not so recommended' alternative 1 version that uses getDerivedStateFromProps, as you are just recomputing the state vars:

getDerivedStateFromProps(props, state) {
  if(props.resetMessages) {
    const company = Object.assign({}, state.company);
    company.id = 0;
    company.messages = [];    
    return {
      company: company
   }
}
Larzan
  • 9,389
  • 3
  • 42
  • 41
3

Try delaying the setState() call until the rendering completes. You can use setTimeout() for this purpose. For example, instead of:

this.resetMsg()

use

setTimeout(() => this.resetMsg())

This will push the resetMsg() call (and hence the setState(), too) to the end of the event queue, letting the render() call complete first.

Marcin Wojnarski
  • 2,362
  • 24
  • 17