How do I stop state element being passed to child component before it is set?

Tags: , , , ,



I’m writing a weather forecast app with React. I’m fetching the data from openweathermap.org API. But to use that I need to know user’s location. So I use other API’s in sequence as well to identify user IP, location, and then weather data according to that location. At each fetching state I update the initial states with information fetched. For example when I fetch Ip, I update userIP in states with setState, then when the latitude and longitude are fetched, I update userLat and userLng as well. Thus, weatherData in states, which is initially an empty array, is the last one being updated. Problem is, render is run every time one of the states is changed. Since one of the child components, to which I am passing the weatherData as props, uses an object in that fetched weatherData array, I get an error because until weatherData is updated, render runs and passes an empty array to that component. I tried to use if statement to check if weatherData is empty array or not before returning results, but somehow it doesn’t work.

Here’s my App.js file:

import React, {Component} from 'react';
import './App.css';
import Mainblock from './Mainblock';
import Hourly from './Hourly';
import Weekly from './Weekly';

class App extends Component {
  constructor() {
    super()

    this.state = {
      userIp: 0,
      cityName: '',
      cityNameNoSpace: '',
      userLat: 0,
      userLng: 0
    }

  }
  

  componentDidMount(){

    fetch("https://geoip-db.com/json/").then((data)=> {

      return data.json();
    }).then((ip)=>{

      this.setState({userIp: ip.IPv4});
      this.locateClient(this.state.userIp);
    });
  }

  locateClient = (clientIp) => {
    fetch(`https://ip-geolocation.whoisxmlapi.com/api/v1?apiKey=at_SECRETAPIKEY&ipAddress=${clientIp}`).then((data)=>{

      return data.json();
    }).then((locationInfo)=>{

      this.setState({userLat: locationInfo.location.lat, userLng: locationInfo.location.lng, cityName: locationInfo.location.city});

      let cityArray = Array.from(locationInfo.location.city);

      let cityNameFiltered = '';
      
      cityArray.forEach((letter)=>{
        cityNameFiltered = cityNameFiltered + letter;
        return cityNameFiltered;
      })

      this.setState({cityNameNoSpace: cityNameFiltered});

      this.getWeatherData(this.state.cityNameNoSpace);

    });
  }

  getWeatherData = (userCity) => {

    fetch(`https://api.openweathermap.org/data/2.5/onecall?lat=${this.state.userLat}&lon=${this.state.userLng}&units=metric&appid=SECRETAPIKEY`).then((data)=>{

      return data.json();
    }).then((weatherInfo)=>{

      this.setState({weatherData: weatherInfo});
    });
  }

  

  render() {

    return (
      <div className="whole-container">
        <div className="lside">
          <Mainblock states={this.state}/>
          <Weekly />
        </div> 
        <Hourly />
      </div>
    );
    
  }
}

export default App;

Answer

Since your Mainblock component is expecting the states prop to be an object with the property weatherData where weatherData should be an array, you can conditionally render the component.

To conditionally render it would look like the following:

render() {
    return (
      <div className="whole-container">
        <div className="lside">
          {Array.isArray(this.state.weatherData) && <Mainblock states={this.state}/> }
          <Weekly />
        </div> 
        <Hourly />
      </div>
    );
  }

The reason that this works is that javascript evaluates boolean expressions and returns the right hand side of the expression when true, otherwise returning false.

> true && 123
< 123
> false && 123
< false


Source: stackoverflow