0

On page load I'm getting the weather data for an api and then displaying it on sidebar then when you click on a city it shows the the city's weather in more detail. So basically I've been passing the data around from parent to child with props. I need to fill the detailed component with some initial data so I'm trying to send the first object in the data array to the child component through props and set it to state but when I try to render it is undefined and I'm not sure why.

It actually seems to be coming back undefined a couple times before setting it but when I try to render it on the page {weather.data.temp} I get 'Cannot read property 'temp' of undefined'.

data

Parent:

const fetchCity = async (city) => {
    const res = await axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${key}`);

    return {
        description: res.data.weather[0].description,
        icon: res.data.weather[0].icon,
        temp: res.data.main.temp,
        city: res.data.name,
        country: res.data.sys.country,
        id: res.data.id,
    };
};

function App() {
    const [data, setData] = useState([]);

    const [activeWeather, setActiveWeather] = useState([]);

    useEffect(() => {
        const fetchCities = async () => {
            const citiesData = await Promise.all(["Ottawa", "Toronto", "Vancouver", "California", "London"].map(fetchCity)).catch((err) => {
                console.log(err);
            });

            setData((prevState) => prevState.concat(citiesData));
        };

        fetchCities();
    }, []);

    const handleClick = (event) => {
        const weather = JSON.parse(event.target.dataset.value);
        setActiveWeather(weather);
    };

    return (
        <div className="App">
            <Header />
            <Container>
                <Row>
                    <Col>
                        <WeatherPanel data={data} handleClick={handleClick} />
                    </Col>
                    <Col>
                        <ActiveWeather activeWeather={activeWeather} data={data[0]} />
                    </Col>
                </Row>
            </Container>
        </div>
    );
}

export default App;

Child

    import React, { useEffect, useState } from "react";
    import { Container, Card } from "react-bootstrap";
    
    const ActiveWeather = (props) => {
        const [weather, setWeather] = useState();
    
        useEffect(() => {
            setWeather(props.data);
        }, [props]);
    
        console.log(weather);
        return (
            <Container>
                <Card>
                    <Card.Header> </Card.Header>
                    {weather.temp}
                </Card>
            </Container>
        );
    };
    
export default ActiveWeather;

Other child

 const WeatherPanel = (props) => {
    return (
        <div>
            <Container fluid>
                <Card style={{ boxShadow: "0  0  10px 2px lightgrey" }}>
                    <Card.Header> Favorite Location</Card.Header>
                    <ListGroup variant="flush">
                        <ListGroup.Item>
                            {props.data.map((item) => (
                                <ListGroup.Item key={item.id} data-value={JSON.stringify(item)} onClick={props.handleClick}>
                                    <img src={`http://openweathermap.org/img/wn/${item.icon}@2x.png`} alt="Weather Icon" />
                                    {item.city + ", " + item.country}
                                </ListGroup.Item>
                            ))}
                        </ListGroup.Item>
                    </ListGroup>
                </Card>
            </Container>
        </div>
    );
};

export default WeatherPanel;
zer0day
  • 236
  • 1
  • 3
  • 11
  • This line - `const weather = JSON.parse(event.target.dataset.value);` - seems really weird to me. Why do you use DOM to store the data? – raina77ow Jul 01 '21 at 21:40
  • Basically I have this card with a few different cities on it and when I click on a certain city I want to display the a more detailed section of the cities weather. I used data-value to pass the value to that handleClick function but it needs to be parsed and for some reason if I parse it anywhere but in app.js it gives me a cross-origin error. I edited to show the other component. – zer0day Jul 01 '21 at 22:27

2 Answers2

0

I think the issue is with your useEffect in the ActiveWeather component. Because you are passing just props as the dependancy of your useEffect, any prop change will trigger it. So in your example activeWeather is probably triggering it, and you are then setting the state of weather to undefined because at mount stage, there isn't any data to pass in.

If you limit the dependence to only props.data. Then that useEffect will only run if something gets passed though the data prop. You can even use an if statement in there to double check that there is actually some data.

useEffect(() => {
  if(props.data){
    setWeather(props.data);
  }
}, [props.data]);

In your parent component you are also passing in data[0] as the data prop. On mount, data is an empty array so if you say data[0] it will be undefined. Maybe wrap the rendering of your ActiveWeather component in an if to check if there is any data

<Col>
   { data.length > 0 &&  <ActiveWeather activeWeather={activeWeather} data={data[0]} /> } 
</Col>

Richard Hpa
  • 2,353
  • 14
  • 23
  • the problem is my props are undefined for some reason, even doing this my props are still undefined. – zer0day Jul 01 '21 at 22:46
  • Just noticed you are also passing data[0] as the data prop which will be undefined at the beginning because you are trying to get the first iteration of an empty array – Richard Hpa Jul 01 '21 at 22:51
0

Issue

Calling setState does not update the state variable immediately. Why?

Solution

Check if weather is defined before accessing it.

Option 1: Use weather?.temp

Option 2: Use an if statement: if (!weather)