Leon Gaban
Leon Gaban

Reputation: 39018

React setState in promise causing infinite loop

Expected

When fetchServices() is called, api.getServices is called and in the promise this.setState is called to change fetchingServices to false. Which then hides the loading spinner animation.

Results

For some reason the App is stuck in an infinite loop:

enter image description here

In my ServicesContainer

constructor(props) {
  super(props);
  this.state = {
    services: props.state.servicesReducer.services,
    fetchingServices: true,
    addingService: false
  }
  this.fetchServices = this.fetchServices.bind(this);
}

The return()

return (
  <div className='services-container'>
    <ul className='services-list'>
      <li>
        <AddServiceContainer />
      </li>
      { this.state.fetchingServices
          ? <div className="icon-spin5 animate-spin"></div>
          : null }

      { this.fetchServices() }
    </ul>
  </div>
)

Finally fetchServices()

fetchServices() {
  console.log('fetchServices')
  api.getServices(12345).then(res => {
    console.log(' api.getServices res:', res)

    this.setState({
      fetchingServices: false
    });
  });
}

Full code

import React, { Component } from 'react'
import { connect } from 'react-redux'

import { AddServiceContainer } from './AddServiceContainer'
import { ServiceCard } from '../../components'
import { getServices } from '../../actions'
import * as api from '../../services/api'

export class ServicesContainer extends Component {
    constructor(props) {
        super(props);
        this.state = {
            services: props.state.servicesReducer.services,
            fetchingServices: true,
            addingService: false
        }
        this.fetchServices = this.fetchServices.bind(this);
    }

    onFormSubmit(e, user) {
        e.preventDefault();
        this.props.searchUser(user)
    }

    fetchServices() {
        console.log('fetchServices')
        api.getServices(12345).then(res => {
            console.log(' api.getServices res:', res)

            this.setState({
              fetchingServices: false
            });
        });
    }

    render() {
        return (
            <div className='services-container'>
                <ul className='services-list'>
                    <li>
                        <AddServiceContainer />
                    </li>
                    { this.state.fetchingServices
                            ? <div className="icon-spin5 animate-spin"></div>
                            : null }

                    { this.fetchServices() }
                </ul>
            </div>
        )
    }
}

const mapStateToProps = (state) => {
    return {
        state
    }
}

const mapDispatchToProps = (dispatch) => {
    return {
        getServices: (services) => { dispatch(getServices(services)) }
    }
}

const ServicesListContainer = ServicesContainer;
export default connect(mapStateToProps, mapDispatchToProps)(ServicesListContainer)

Upvotes: 5

Views: 6210

Answers (2)

Prakash Sharma
Prakash Sharma

Reputation: 16472

Whenever you do setState, the render method is called again. Now problem here is that you are calling fetchServices() method inside the render method. Now whenever fetchServices() is called it calls an api. When the result of the api come, you are setting the state using setState, which causes rerender(i.e. your render method is called again), which calls the fetchServices() again. This is why it is going in infinite loop.

The solution: You should call your fetchServices() in componentWillMount/componentDidMount method like this:

import React, { Component } from 'react'
import { connect } from 'react-redux'

import { AddServiceContainer } from './AddServiceContainer'
import { ServiceCard } from '../../components'
import { getServices } from '../../actions'
import * as api from '../../services/api'

export class ServicesContainer extends Component {
    constructor(props) {
        super(props);
        this.state = {
            services: props.state.servicesReducer.services,
            fetchingServices: true,
            addingService: false
        }
        this.fetchServices = this.fetchServices.bind(this);
    }

    componentWillMount(){
       this.fetchServices();
    }
    onFormSubmit(e, user) {
        e.preventDefault();
        this.props.searchUser(user)
    }

    fetchServices() {
        console.log('fetchServices')
        api.getServices(12345).then(res => {
            console.log(' api.getServices res:', res)

            this.setState({
              fetchingServices: false
            });
        });
    }

    render() {
        return (
            <div className='services-container'>
                <ul className='services-list'>
                    <li>
                        <AddServiceContainer />
                    </li>
                    { this.state.fetchingServices
                            ? <div className="icon-spin5 animate-spin"></div>
                            : null }

                </ul>
            </div>
        )
    }
}

const mapStateToProps = (state) => {
    return {
        state
    }
}

const mapDispatchToProps = (dispatch) => {
    return {
        getServices: (services) => { dispatch(getServices(services)) }
    }
}

Upvotes: 6

Kamil Socha
Kamil Socha

Reputation: 299

You should never fetch data in render function. You should do it in componentDidMount function.

render is called after each state or props change, and if you execute an api call in render function, it will trigger setState and by doing so - render again and again and again...

See link

Upvotes: 6

Related Questions