Como cronometrar corretamente a renderização de dados em reagir?

8

Estou tentando extrair dados do Open Data para montar um rápido mapa de calor. No processo, quero adicionar algumas estatísticas. Quase tudo corre bem porque tenho os dados e sou capaz de renderizar o mapa, mas não tenho certeza de como lidar com os cálculos depois de obter os dados, pois leva tempo para os dados chegarem. Como faço para configurar as coisas para que Posso executar uma função em uma variável de estado se ainda não tiver recebido dados necessariamente? Atualmente, estou recebendo um nulo como o número que é passado como adereços ao StatCard.

Abaixo estão minhas tentativas:

App.js

  import React, { Component } from 'react';
import Leaf from './Leaf';
import Dates from './Dates';
import StatCard from './StatCard';
import classes from './app.module.css';

class App extends Component {

  constructor(props) {
    super(props);
    this.state = {
      data:[],
      cleanData:[],
      dateInput: '2019-10-01',
      loading: false,
      totalInspections: null,
      calculate: false
    };
  }

  componentDidMount() {
    try {
      this.fetchData();
    } catch (err) {
      console.log(err);
      this.setState({
        loading: false
      })
    }
  }


  fetchData=()=>{
    const requestData = async () => {
      await fetch(`https://data.cityofnewyork.us/resource/p937-wjvj.json?$where=latitude > 39 AND latitude< 45 AND inspection_date >= '${this.state.dateInput}'&$limit=50000`)
        .then(res => res.json())
        .then(res =>
          //console.log(res)
          this.setState({ data: res, loading: true})
        )
    }

    const  calculateInspections = () => {
      this.setState({totalInspections: this.state.data.length})
    }

    //call the function
    requestData();

    if(this.state.data) {
      calculateInspections();
    }
  }

  handleDateInput = (e) => {
    console.log(e.target.value);
    this.setState({dateInput:e.target.value, loading: false}) //update state with the new date value
    this.updateData();
    //this.processGraph(e.target.value)
  }

  updateData =() => {
    this.fetchData();
  }

  LoadingMessage=()=> {
    return (
      <div className={classes.splash_screen}>
        <div className={classes.loader}></div>
      </div>
    );
  }


  //inspection_date >= '${this.state.dateInput}'& 
 // https://data.cityofnewyork.us/resource/p937-wjvj.json?$where=inspection_date >= '2019-10-10T12:00:00' 

  render() {



    return (
      <div>

        <div>{!this.state.loading ? 
              this.LoadingMessage() : 
              <div></div>}
        </div>

        {this.state.totalInspections && <StatCard totalInspections={this.state.totalInspections} /> }

          <Dates handleDateInput={this.handleDateInput}/>
          <Leaf data={this.state.data} />

      </div>
    );
  }
}

export default App;

StatCard.js

import React from 'react';


const StatCard = ( props ) => {

    return (
        <div >
            { `Total Inspections: ${props.totalInspections}`}
        </div>
    )
};

export default StatCard;

Tentativa de reparo

   componentDidMount() {
    try {
      this.fetchData();
    } catch (err) {
      console.log(err);
      this.setState({
        loading: false
      })
    }
  }


  componentDidUpdate () {
    if(this.state.data) {
      this.setState({totalInspections: this.state.data.length})
    }
  }

  fetchData= async ()=>{
    const requestData = () => {
    fetch(`https://data.cityofnewyork.us/resource/p937-wjvj.json?$where=latitude > 39 AND latitude< 45 AND inspection_date >= '${this.state.dateInput}'&$limit=50000`)
        .then(res => res.json())
        .then(res =>
          //console.log(res)
          this.setState({ data: res, loading: true})
        )
    }
    //call the function
    await requestData();

  }
LoF10
fonte
this.setState({ data: res, loading: true})um erro de digitação na requestDatafunção? não deve loadingser definido como falsequando os dados são buscados?
thgaskell
sim é um erro de digitação
LoF10
Você tem certeza de que o uso de parâmetros de consulta corresponde à documentação da API?
Jurrian 18/11/19
Sim, como você pode ver no código, os dados são buscados, esse não é o problema. Eu tenho os dados. O problema é que não tenho certeza de onde e quando executar o repeatInspections (), pois ele precisa ser executado quando eu receber todos os dados da API e ser passado como suporte ao statCard.
LoF10 18/11/19
Por favor, envie-me o link
codesandbox

Respostas:

0

Renderize somente <StatCard />se você tiver os dados necessários:

{this.state.totalInspections && <StatCard totalInspections={this.state.totalInspections} /> }
Chase DeAnda
fonte
Eu tentei isso, mas sem dados. Também tentou executar o cálculo apenas se this.state.data existir em componentDidMount, mas sem sorte. O que eu recebo agora é apenas o número 0 onde o StatCard seria #
LoF10 15/11/19
1
Entendo, é porque você não entende como o comportamento promissor / assíncrono funciona. Você precisa, em await requestData()vez de adicioná-lo diretamente à função requestData, e adicionar async fetchData = () => {}. Melhor ainda, fetchDatasó deve buscar os dados e armazená-los no estado. Então você deve usar componentDidUpdatepara disparar calculateInspections()apenas quando this.state.datamudanças
Perseguição DeAnda
Ei Chase, tentando operacionalizar o que você apontou, embora eu admita que ainda esteja aprendendo, não tenho certeza de como exatamente implementar. eu adicionei minha tentativa à minha pergunta se você pudesse dar uma olhada e me apontar na direção certa?
LoF10
0

Primeiro de tudo , não acho que você precise de uma função separada calculateInspections(). Você pode colocar essa lógica no thenretorno de chamada.

fetchData = () => {
  fetch(`https://data.cityofnewyork.us/resource/p937-wjvj.json?$where=latitude > 39 AND latitude< 45 AND inspection_date >= '${this.state.dateInput}'&$limit=50000`)
    .then(res => res.json())
    .then(data => {
      this.setState({
        data: data,
        loading: true,
        totalInspections: this.state.data.length
      })
    })
}

Em segundo lugar , a configuração this.state.totalInspectionsé efetivamente redundante, pois você pode:

{this.state.data && <StatCard totalInspections={this.state.data.length} /> }

Por fim, evite usar o componentDidUpdate()gancho quando for novo para reagir. Na maioria das vezes você acaba atirando no próprio pé.

Atualmente, seu reparo por tentativa acabou de colocar você em um loop infinito de renderização. Isso acontece porque sempre que você chama setState(), ele chama componentDidUpdate()o gancho do ciclo de vida após a renderização. Mas dentro de componentDidUpdate()você ligar novamente setState() , o que induz uma chamada de acompanhamento para o mesmo gancho de ciclo de vida e, portanto, o loop continua.

Se você deve usar componentDidUpdate()e ligar para setState()dentro, regra geral, sempre coloque uma condição de parada à frente. No seu caso, será:

componentDidUpdate () {
  if (this.state.data) {
    if (this.state.totalInspections !== this.state.data.length) {
      this.setState({ totalInspections: this.state.data.length })
    }
  }
}
hackape
fonte
0

Portanto, seu problema é que o estado isLoading precisa ser definido SINCRONIZAMENTE antes de qualquer chamada assíncrona.

Então, no seu componentDidMount:

componentDidMount() {
    try {
      this.setState({ loading: true }); // YOU NEED TO SET TRUE HERE
      this.fetchData();
    } catch (err) {
      console.log(err);
      this.setState({
        loading: false
      })
    }
}

Isso garante o carregamento assim que você faz a ligação. Em seguida, sua ligação é feita e essa parte é ASSÍNCRONA. Assim que os dados chegam, o carregamento é concluído

.then(data => {
  this.setState({
    data: data,
    loading: false, // THIS NEEDS TO BE FALSE
    totalInspections: this.state.data.length
  })
})

Além disso: seu método de renderização pode ter várias instruções de retorno Em vez de ter jsx condicional, retorne seu layout de carregamento

render() {

    if (this.state.loading) {
        return <div> I am loading </div>
    }

    return <div> Proper Content </div>;

}
Daniel Duong
fonte
0

Aqui está a minha solução.

class App extends Component {

    constructor(props) {
        super(props);
        this.state = {
            data: [],
            dateInput: '2019-10-01',
            loading: false,
            error: false
        };
    }

    async componentDidMount() {
        try {
            await this.fetchData(this.state.dateInput);
        } catch (err) {
            this.setState({ loading: false, error: true });
        }
    }

    fetchData = (date) => new Promise(resolve => {
        this.setState({ loading: true });
        fetch(`https://data.cityofnewyork.us/resource/p937-wjvj.json?$where=latitude > 39 AND latitude< 45 AND inspection_date >= '${date}'&$limit=50000`)
            .then(res => res.json())
            .then(res => {
                this.setState({ data: res, loading: false, error: false });
                resolve(res.data);
            });
    })

    handleDateInput = e => {
        this.setState({ dateInput: e.target.value }) //update state with the new date value
        this.fetchData(e.target.value);
    }

    render() {
        const { loading, data } = this.state;
        return (
            <div>
                {loading && (
                    <div className={classes.splash_screen}>
                        <div className={classes.loader}></div>
                    </div>
                )}
                {data && <StatCard totalInspections={data.length} />}
                <Dates handleDateInput={this.handleDateInput} />
                <Leaf data={data} />
            </div>
        );
    }
}

TopW3
fonte