'react components rendered do not show up with dynamic data

My problem is, that my renders are one step behind or otherwise won't even render at all.

I am new to react that is why I was trying to make a weather app which took the location by an input form and rendered corresponding location data(maximum of 5), with the weather, I am using openweathermap. I have made three components

  1. Search - which gets the array of locations (from openweather geo getter api) and stores it into the locations state I made with context.
  2. Weather - which iterates over the locations array and calls a function weatherSetter for every location and pushes it into a newWeather and finally puts it into weathers state. It also finally iterates over the weathers array as well and renders the weather for each element.
  3. WeatherItem - which just takes the data from the weather component and renders it.

These are the code for the program.

Search component

export default function Search() {
  const context = useContext(locationContext);
  const { setLocations } = context;
  const [ location, setLocation ] = useState(""); 

  const onChange = (e) => {
    setLocation(e.target.value);
  }

  const searchGeoLocation = async (event) => {
    event.preventDefault();
    const fetchedData = await fetch(`http://api.openweathermap.org/geo/1.0/direct?q=${location}&limit=5&appid={key}`);
    const data = await fetchedData.json();
    // console.log(data); 
    setLocations([...data]);
  }
  
  return (
    <div className='container'>
      <form> 
        <div className="mb-3">
          <label htmlFor="exampleInputEmail1" className="form-label">Type the State</label>
          <input type="text" className="form-control" id="location" aria-describedby="emailHelp" name='location' value={location} onChange={onChange} />
        </div>
        <button type="submit" className="btn btn-primary" onClick={searchGeoLocation}>Submit</button>
        {/* <button type="submit" className="btn btn-primary" onClick={(e) => { e.preventDefault();console.log(locations)}}>Tiger</button> */}

      </form>
    </div>
  )
}

For the Weather component, I used useEffect to run only when locations changes, which only happens when submit is pushed, and call weatherSetter which populates the weathers state with an array of new weather data of the locations respectively.

export default function Weather(props) {
  const context = useContext(LocationContext);
  const { locations, weathers, weatherSetter } = context;

   useEffect(() => {
    weatherSetter();
  }, [locations])
  
  return (
    <>
    <div>
    {/* {console.log(weathers)} */}
    {
      weathers.map((weather) => {
        return <div className='col-md-4'>
          <WeatherItem key={weather.id} weather={weather} />
        </div>
          
    })}
    </div>
    </>
  )
}

For my context states,

const locationState = (props) => {
  const dummyData = [
    {id: "1", main: "Dummy", description: "Dummy State", icon: "10d"}, 
    {id: "2", main: "Dummy", description: "Dummy State", icon: "10d"},
    {id: "3", main: "Dummy", description: "Dummy State", icon: "10d"}, 
    {id: "4", main: "Dummy", description: "Dummy State", icon: "10d"},
    {id: "5", main: "Dummy", description: "Dummy State", icon: "10d"}
  ]
  const [ weathers, setWeathers ] = useState(dummyData);
  
  const [ locations, setLocations ] = useState([{ lat: 21, lon: 91}]);

  const getWeather = async (lat,lon) => {
    const fetchedData = await fetch(`https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid={key}`);
    const data = await fetchedData.json();
    return data;
  }

  const weatherSetter = () => {
    let newWeathers = [];
    locations.forEach(async (location) => {
      const fetchedData = await getWeather(location.lat, location.lon).then(result => result);
      newWeathers.push(fetchedData.weather[0]);
    })
    // console.log(newWeathers)
    setWeathers(newWeathers);
  }

  return (
    <LocationContext.Provider value={{ weathers, setWeathers, locations, setLocations, getWeather, weatherSetter }}>
      {props.children}
    </LocationContext.Provider>
  );
}

export default locationState;

The consistent problem I see is that the weather items will not render at all and if I use settimeout inside useEffect, I will be able to see the items being rendered but after the timeout the items will disappear. I have tried multiple ways to do this but I just have not been able to. I believe the problem is due to my fault in not using useEffect correctly and the asynchronous nature of components, or maybe an entire different issue, but I am not able to identify what it is.

So if maybe someone can help me, it will be very much appreciated.



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source