S
S
Stakkato2020-07-06 22:54:01
React
Stakkato, 2020-07-06 22:54:01

Why is the state not updated in the component?

Hello! While learning React, I am making an application to display the current weather and ran into a problem.

There is a class that makes a weather request and puts the necessary data from the response into the data object:

export default class AskForWeather {
    getNeededData = async () => {
        const city = "ulyanovsk"; //"479123";
        const url = "http://api.openweathermap.org/data/2.5/weather?q=" + city + "&appid=b1b35bba8b434a28a0be2a3e1071ae5b&units=imperial";
         let data = {};

        await fetch(url).then((res) => res.json()).then(json => {
            data = { temperature: json.main.temp }
        })
        return data;
  }
}

-------------------------------------------------- --------------------
There is a main file where class AskForWeather is imported. The state is updated with data from the data object. In the Main component, temperature is passed as a prop.

import React, { Component } from 'react';
import Main from "../main/main.js" 
import AskForWeather from "../../service/parser.js"

export default class App extends Component {
    constructor(props) {
        super(props);
        this.state = { weather: null }
    }

    askForWeather = new AskForWeather();

    openData = () => {
        this.setState(() => {
            return { weather: this.askForWeather.getNeededData() }
        });
    }

    componentDidMount() {
        this.openData();
    }

    render() {
        return(
            <div className= "container">
                <Header />
                <Main temperature={this.state.weather.temperature}/>
                <Additional />
            </div>
        );
    }
}


-------------------------------------------------- -----------
Well, Main, which accepts the temperature prop and displays it in the layout

import React, { Component } from 'react';

const Main = (props) => {
    const temperature = props.temperature;
    return (
        <section className="main">
            <div className="temperature">
                <span>{temperature} C</span>
            </div>
            <div className="weather-desc">
                <img className="weather" src="" alt="#"></img>
                <span className="desc">Тепло</span>
            </div>
        </section>
    )
}
export default Main;


As a result, the error TypeError: this.state.weather is null.
And I can't fight it. Apparently AskForWeather receives data, but for some reason the state is not updated in the main file. And why is this happening? Where did I go wrong?

Answer the question

In order to leave comments, you need to log in

3 answer(s)
0
0xD34F, 2020-07-06
@Stakkato

let data = {};

await fetch(url).then((res) => res.json()).then(json => {
    data = { temperature: json.main.temp }
})
return data;

What kind of game is this? Let it be .return fetch(url).then(r => r.json());
this.setState(() => {
    return { weather: this.askForWeather.getNeededData() }
});

Throw a promise into a state. Somehow I doubt that this was intended. Change to
this.askForWeather.getNeededData().then(weather => {
  this.setState(() => ({ weather }));
});

<Main temperature={this.state.weather.temperature}/>

As long as weather is null, attempts to read the properties will result in an error. So that
{this.state.weather && <Main temperature={this.state.weather.main.temp} />}

S
Sergei Chamkin, 2020-07-06
@Sergei1337

In the Main prop, pass this.state.weather.temperature, that is, the field of the weather object, but it does not exist, since it is initially null, but the render method does not wait until the request is made and immediately tries to pass the prop when calling render.
Do a null check on the this.state.weather object or add a ready field to the state

S
Stakkato, 2020-07-08
@Stakkato

Thanks for the replies, your tips worked!
I changed it, however, and as a result the code became like this:

export default class App extends Component {
    constructor(props) {
        super(props);
        this.state = {
            weather: {},
            isReadyToRender: false
        }
    }

    askForWeather = new AskForWeather();

    openData = () => {
        this.askForWeather.getNeededData().then(weather => {
            this.setState({
                weather: weather,
                isReadyToRender: true // стейт обновился, флаг стал true
            });
        })
        
    }

    componentDidMount() {
        let timerId = setTimeout(() => {
           this.openData(); 
        }, 5000);
        this.openData();
    }

    render() {
        if (this.state.isReadyToRender) { //если стейт обновлен и флаг в нем изменен на true
            return(
                <div className= "container">
                    <Header />
                    <Main temperature={this.state.weather.main.temp} />
                    <Additional />
                </div>
            );
        } else {
            return null;
        }
        
    }

}

That is, until the state is updated, nothing will be rendered. As soon as the data came to the state, the state was drawn. Did you do it right?
And yet, it turns out that in the render () method, instead of if, one should use a promise that waits for the isReadyToRender flag to change and only then returns the layout? Or is the current version also normal (for a beginner =))?

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question