react: Calling setState in render causes infinite loop

This may seem really silly to do to call setState in render. However it’s possible for this to happen if a component has a callback which is being called immediately during render and in your callback handler you call setState.

About this issue

  • Original URL
  • State: closed
  • Created 9 years ago
  • Reactions: 1
  • Comments: 26 (2 by maintainers)

Commits related to this issue

Most upvoted comments

I ran into this by accident once… I had onClick={this.setState({inProgress: true})} instead of onClick={() => this.setState({inProgress: true})}. It would be nice if React detected the problem and threw an error “setState detected in render() of SomeComponent” instead of going into the infinite loop.

Again this is an oversimplification and what Foo might be doing is dumb but react still shouldn’t call render immediately after setState in the same reconciliation cycle.

The fact that we support calling setState in componentDidMount or componentDidUpdate is not an accident – doing so in order to trigger an update is sometimes useful.

In general, this should never happen; you should not be doing anything remotely related to modifying any component’s state within a render function.

I’m curious what your use case is, for having a callback which does a setState; mind sharing?

  1. It is absolutely fine to call setState from componentDidMount if you have to read something from the DOM. It’s not supposed to be pure. In fact the measurement use case is one of the reasons it exists.

  2. React was designed with this use case in mind. There shouldn’t be a blip because React processes setState from componentDidMount synchronously to avoid this problem.

There are multiple Child1 and Child2 elements with different values, so no, I don’t know mRecord in the parent. But I figured out my issue: I should have <button onClick={()=>this.props.init(this.props.mRecord)}/> instead of <button onClick={this.props.init(this.props.mRecord)}/>

I have a simple solution, compares the state before you change, it worked for me. I just “setState” the state if the new state is different than the current.

This is an oversimplification of the code but:

handleSomething(value) {
  this.setState({
    something: value
  });
}

render() {
  return <Foo 
    someProp={this.props.someValue}
    onCallback={this.handleSomething} 
  />;
}

When Foo receives props it calls onCallback which in turn calls handleSomething. Which will cause render to be called immediately. Then once again handleSomething gets called then render and so on.

Again this is an oversimplification and what Foo might be doing is dumb but react still shouldn’t call render immediately after setState in the same reconciliation cycle.

I realize this is an old ticket and there might be more idiomatic ways of doing things in React now - but I just hit an issue which is related, so figured here’s as good a place as any to comment 😉

What’s the best way to handle situations where you need to measure after the component has been mounted? I’d prefer to avoid ref because there’s no need to actually call the component. Also, when dealing with custom renderers I’m not sure how to implement ref and furthermore - CSS tricks like ‘visible’ may not apply.

Here’s an example of what I mean:

class MyComponent extends React.Component {
    private instanceWidth:number;

    constructor(props) {
        super(props)
        this.state = {}
    }
   
    componentDidMount() {
        this.setState({
            instanceWidth: this.instanceWidth
        })
    }

    render() {
        return <SomeElement 
            onAdded={instance => this.instanceWidth = instance.width}
            x={this.state.instanceWidth === undefined ? 0 : window.innerWidth - this.state.instanceWidth}
            {...props}
        />
    }
}

The issues I have with this approach are:

  1. It’s bordering on like setState within render. Not really since it will only happen the first time via cDM, but still… feels icky with that extra private var and… I dunno… just feels odd?

  2. There’s going to be a blip where the wrong position is truly rendered. The only way around this I can think of is to manage it via visual tricks like setting opacity.

I’d love to hear thoughts on why this is or isn’t the right way to do it. Thanks!