@Stakkato

Почему в компоненте не обновляется стейт?

Здравствуйте! Изучая Реакт, делаю приложение для отображения текущей погоде и столкнулся с проблемой.

Есть класс, который делает запрос погоды и и необходимые данные из ответа засовывает в объект data:

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;
  }
}

-------------------------------------------------------------------------------------
Есть основной файл, куда импортируется class AskForWeather. Стейт обновляется данными из объекта data. В компоненте Main пропсом передается temperature.

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>
        );
    }
}


-------------------------------------------------------------
Ну и Main, принимающий пропс temperature и отображающий его в верстке

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;


В итоге ошибка TypeError: this.state.weather is null.
И никак не могу ее побороть. Судя по всему AskForWeather получает данные, но в основном файле почему-то не обновляется стейт. А из-за чего такое происходит? Где я ошибся?
  • Вопрос задан
  • 91 просмотр
Решения вопроса 1
0xD34F
@0xD34F
let data = {};

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

Это что за дичь? Пусть будет return fetch(url).then(r => r.json());.

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

Засовываете промис в стейт. Что-то сомневаюсь, что так и было задумано. Меняйте на

this.askForWeather.getNeededData().then(weather => {
  this.setState(() => ({ weather }));
});

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

Пока weather имеет значение null, попытки прочитать свойства будут приводить к ошибке. Так что

{this.state.weather && <Main temperature={this.state.weather.main.temp} />}
Ответ написан
Пригласить эксперта
Ответы на вопрос 2
В пропс Main передаете this.state.weather.temperature, то есть поле объекта weather, но его не существует, так как изначально он null, но метод рендер не ждет, пока совершится запрос и сразу пытается передать пропс при вызове render.
Сделайте проверку на null объекта this.state.weather или добавьте в состояние поле готовности
Ответ написан
@Stakkato Автор вопроса
Спасибо за ответы, по вашим подсказкам все заработало!
Я его, правда, изменил и в итоге код стал такой:
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;
        }
        
    }

}

То есть, пока стейт не обновится, ничего рендерится не будет. Как только в стейт пришли данные, стейт отрисовался. Правильно сделал?
И еще, получается, в методе render() вместо if надо бы использовать промис, который ожидает изменения флага isReadyToRender и только потом возвращает верстку? Или текущий вариант тоже норм(для новичка =))?
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы