Search code examples
reactjsstatefetch-apilifecycle

How to properly fetch() JSON in React


I'm trying to fetch some JSON data from a URL. I'm then setting my state to be a portion of that data.

In my render method, I'd like to the display that data using map()

The issue I'm facing is that because it takes some time to fetch the data, the state is still set to null by the time it tries to render it.

Another issue I'm having is that my getData() function seems to fire repeatedly every few seconds. If I add a console log at the end it logs it over and over again.

Would anyone be able to let me know if they can see me doing something wrong?

My code is as follows:

getData = () => {
    let _this = this
    fetch('https://my-data-link.com')
    .then(
      response => {
        if (response.status !== 200) {
          console.log('Looks like there was a problem. Status Code: ' +
            response.status);
          return;
        }

        // Examine the text in the response
        response.json().then(
          data => {
            _this.setState({data: data.searchResults.filter(d => d.salesInfo.pricing.monthlyPayment <= _this.state.monthlyMax)})
          }
        );
      }
    )
    .catch(function(err) {
      console.log('Fetch Error :-S', err);
    });
    
    // Adding a console log here is when I noticed that the code keeps firing.
  }

  renderData = () => {
    let vehicles = "Loading..."
    let _this = this
    this.getData()
    setTimeout(function(){
      vehicles = _this.state.data.map(vehicle => (
        <h1>{vehicle.make}</h1>
      ))
      return vehicles
    }, 6000);

    return vehicles
  }
  
  render() {
    return (
      {this.state.formSubmitted > 0 &&
        <div>
          <h3 className="ch-mt--4">Recommended vehicles</h3>
          {this.renderData()}
        </div>
      }
    )
  }


Solution

  • You have two issues

    First: You are calling getData in render method and in getData you are calling setState which essentially triggers a loop. Trigger it in componentDidMount if the it needs to fire just once on component mount.

    Second: Instead of setTimeout which isn't reliable, you should initialise the state data to be empty array in constructor

    constructor(props) {
        super(props);
        this.state = {
            data: [];
        }
    }
    componentDidMount() {
         this.getData();
    }
    
    getData = () => {
        let _this = this
        fetch('https://my-data-link.com')
        .then(
          response => {
            if (response.status !== 200) {
              console.log('Looks like there was a problem. Status Code: ' +
                response.status);
              return;
            }
    
            // Examine the text in the response
            response.json().then(
              data => {
                _this.setState({data: data.searchResults.filter(d => d.salesInfo.pricing.monthlyPayment <= _this.state.monthlyMax)})
              }
            );
          }
        )
        .catch(function(err) {
          console.log('Fetch Error :-S', err);
        });
    
    
      }
    
      renderData = () => {
        let vehicles = "Loading..."
        if(this.state.data.length === 0) {
            return vehicles;
        }
        return this.state.data.map(vehicle => (
            <h1>{vehicle.make}</h1>
        ));
      }
    
      render() {
        return (
          {this.state.formSubmitted > 0 &&
            <div>
              <h3 className="ch-mt--4">Recommended vehicles</h3>
              {this.renderData()}
            </div>
          }
        )
      }