最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to properly time data rendering in react? - Stack Overflow

programmeradmin0浏览0评论

I am attempting to pull data from Open Data to put together a quick heat map. In the process, I want to add some stats. Almost everything runs well in that I have the data and am able to render the map, but I am unsure how to deal with calculations once I get the data since it takes time for data to e in. How do I set things up so that I can run a function on a state variable if it hasn't necessarily received data yet? Currently I am getting a null as the number that is passed as props to StatCard.

Below are my attempts:

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

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


  fetchData=()=>{
    const requestData = async () => {
      await fetch(`.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}'& 
 // .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;

Attempt Repair

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


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

  fetchData= async ()=>{
    const requestData = () => {
    fetch(`.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();
   
  }

I am attempting to pull data from Open Data to put together a quick heat map. In the process, I want to add some stats. Almost everything runs well in that I have the data and am able to render the map, but I am unsure how to deal with calculations once I get the data since it takes time for data to e in. How do I set things up so that I can run a function on a state variable if it hasn't necessarily received data yet? Currently I am getting a null as the number that is passed as props to StatCard.

Below are my attempts:

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

  ponentDidMount() {
    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;

Attempt Repair

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


  ponentDidUpdate () {
    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();
   
  }
Share Improve this question edited Sep 24, 2022 at 3:53 starball 52.5k32 gold badges217 silver badges905 bronze badges asked Nov 15, 2019 at 20:27 LoF10LoF10 2,1273 gold badges30 silver badges80 bronze badges 5
  • Is this.setState({ data: res, loading: true}) a typo in the requestData function? shouldn't loading be set to false when the data is fetched? – thgaskell Commented Nov 15, 2019 at 23:46
  • yeah its a typo – LoF10 Commented Nov 15, 2019 at 23:59
  • Are you sure the use of query parameters is corresponding with the API documentation? – Jurrian Commented Nov 18, 2019 at 18:52
  • Yes, as you can see in the code the data is fetched, that is not the problem. I have the data. The issue is I am unsure where and when to run calculateInspections() because it needs to run when I have received all of the API data and then be passed as props to the statCard. – LoF10 Commented Nov 18, 2019 at 19:04
  • Please send me the codesandbox link – TopW3 Commented Nov 24, 2019 at 21:32
Add a ment  | 

5 Answers 5

Reset to default 1

So your problem is that isLoading state needs to be set synchronously before any async calls.

So in your ponentDidMount:

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

This ensures loading as soon as you make the call. Then your call is made and that part is asynchronous. As soon as data es through, the loading is done:

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

Furthermore, your render method can have multiple return statements. Instead of having conditional JSX, return your loading layout:

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

    return <div> Proper Content </div>;
}

Only render <StatCard /> if you have the data you need:

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

First of all, I don't think you need a separate function calculateInspections(). You can put that logic in the then callback.

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

Secondly, setting this.state.totalInspections is effectively redundant, since you can simple do:

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

Lastly, avoid using ponentDidUpdate() hook when you're new to react. Most of the time you end up shooting yourself in the foot.

Currently your Attempt Repair just got you into an infinite render loop. This happens because whenever you call setState(), it'll call ponentDidUpdate() lifecycle hook after rendering. But within ponentDidUpdate() you call again setState(), which induces a follow-up call to the same lifecycle hook, and thus the loop goes on and on.

If you must use ponentDidUpdate() and call setState() inside, rule of thumbs, always put a stop-condition ahead of it. In you case, it'll be:

ponentDidUpdate () {
  if (this.state.data) {
    if (this.state.totalInspections !== this.state.data.length) {
      this.setState({ totalInspections: this.state.data.length })
    }
  }
}

Here is my solution.

class App extends Component {

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

    async ponentDidMount() {
        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>
        );
    }
}

There are two ways of achieving this:

  1. You can put calculator in ponentDidUpdate() and write a condition to just calculate once

ponentDidUpdate(prevProps, prevState) {
    const data = this.state.data;

    // this line check if we have data or we have new data,
    // calculate length once
    if (data.length || !isEqual(data, prevState.data)) {
        calculateInspections()
    }
}

// isEqual() is a lodash function to pare two object or array

  1. You can stop your rendering until data is fetched

async ponentDidMount() {
      await fetchData()
    }

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,
            totalInspections: res.length
        })
        )
    }
    // in above situation you just setState when you are sure
    // that data has e

    //call the function
    requestData();
}

发布评论

评论列表(0)

  1. 暂无评论