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 therequestData
function? shouldn'tloading
be set tofalse
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
5 Answers
Reset to default 1So 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:
- 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
- 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();
}