React - get data with axios - reactjs

In my react app that is based on class components, My response API got from open weather fixes after several lags.
this is my state
class Weather extends Component {
constructor(props) {
super(props);
this.state = {
weatherData: undefined,
weatherDescription: undefined,
};
}
My thinking was that when my componentDidMount,
weather API getting from openWeather and set it in state
componentDidMount() {
axios
.get(
`http://api.openweathermap.org/data/2.5/weather?id=someCityId&units=metric&appid=myApiKey`
)
.then((response) => {
if (response.request.status === 200) {
this.setState({
weatherData: response.data.main.temp,
weatherDescription: response.data.weather[0].description,
weatherTextDisplay: this.state.airConditionsText.filter((item)=>{
return item["id"] === response.data.weather[0].id
})
});
}else{throw Error('No internet')}
})
.catch(error => Error.message)
and I want to update data when the city is changing, in componentDidUpdate the data get again from the openWeather
componentDidUpdate() {
axios
.get(
`http://api.openweathermap.org/data/2.5/weather?id=someCityId&units=metric&appid=myApiKey`
)
.then((response) => {
if (response.request.status === 200) {
this.setState({
weatherData: response.data.main.temp,
weatherDescription: response.data.weather[0].description,
weatherTextDisplay: this.state.airConditionsText.filter((item)=>{
return item["id"] === response.data.weather[0].id
})
});
}else{throw Error('No internet')}
})
.catch(error => Error.message)
}
But the problem is that when my response receives, it faces a lag that causes data jumps several times to previous data and new data until it fixes

I do not completely understand the question, but this 'lags' because the action of fetching something from an external source is async and needs time to complete.
As for the second 'part' of displaying the loading text you have to set a variable (preferably in state which indicates the loading state of this component)
eg.
constructor(props) {
super(props);
this.state = {
loading: false,
airConditionsText: null,
// Other stuff you have in state
};
}
componentDidUpdate() {
this.setState({loading: true}) // Start of loading
axios
.get(
`http://api.openweathermap.org/data/2.5/weather?id=${this.state.inputId}&units=metric&appid=myApiKey`
)
.then((response) => {
if (response.request.status === 200) {
this.setState({
weatherData: response.data.main.temp,
weatherDescription: response.data.weather[0].description,
weatherTextDisplay: this.state.airConditionsText.filter((item)=>{
return item["id"] === response.data.weather[0].id
})
});
}else{throw Error('No internet')}
})
.catch(error => Error.message)
.finally(() => this.setState({loading: false})) // End of loading
.finally is being trigger once the async operation (fetching the data from weatherAPI) finishes with either error or success which is the time to stop loading.
Then you can use this.state.loading in component render to show loading text
eg.
render() {
return (
<div>
{this.state.loading
? <div> Loading... </div>
: <div>{this.state.airConditionsText}</div> // other stuff you want to display
}
</div>
);
}

Related

React - Trying to update Fetch call in Parent Component based off of User Input in Child Component

So I am still very much a beginner when it comes to React. I am trying to build an application where the user inputs their location, which would then dynamically update the URL within my fetch call. Based off the results of the fist fetch call, I would then dynamically update a second fetch call, to a different API, providing me with the information needed.
As it stands right now, both Fetch calls are properly working, when provided the right information. The problem I am currently running into is, I don't believe my parent component is re-rendering with the update information. I am trying to console log one of my states but it keeps coming back as blank. The weird part is, the other state that is being created within the child component, is coming back with the right information. Any help would be greatly appreciated!
APP.JS
import Home from './Home/Home';
import Userinput from './UserInput/Userinput';
const url =
'https://api.openuv.io/api/v1/uv?lat=-33.34&lng=115.342&dt=2018-01-24T10:50:52.283Z';
const url1 = `http://www.mapquestapi.com/geocoding/v1/address?key=${process.env.REACT_APP_MAP_API_KEY}`;
class App extends Component {
constructor(props) {
super(props);
this.state = {
uvIndex: '',
lat: '',
long: '',
inputValue: '',
};
this.handleInputValue = this.handleInputValue.bind(this);
}
handleInputValue(val) {
this.setState({ inputValue: val });
}
componentDidMount() {
let newVal = this.state.inputValue;
fetch(`${url1}&location=${newVal}`, {
method: 'GET',
headers: {
'content-type': 'application/json',
},
})
.then((res) => res.json())
.then((res) => {
this.setState({
lat: res.results[0].locations[0].latLng.lat,
long: res.results[0].locations[0].latLng.lng,
});
// this.setState({ uvIndex: res });
})
.catch((err) => {
console.error(err);
});
fetch(url, {
method: 'GET',
headers: {
'content-type': 'application/json',
'x-access-token': `${process.env.REACT_APP_UV_API_KEY}`,
},
})
.then((res) => res.json())
.then((res) => {
console.log(res);
this.setState({ uvIndex: res });
})
.catch((err) => {
console.error(err);
});
}
render() {
console.log(this.state.lat); #this state comes back as blank
console.log(`${url1}&location=${this.state.inputValue}`); # this state comes back with the update userinput
return (
<div>
<header>
<Home />
</header>
<div>
<Userinput handleInput={this.handleInputValue} />
</div>
</div>
);
}
}
export default App;
Userinput.js
class Userinput extends Component {
constructor(props) {
super(props);
this.state = {
inputVal: '',
};
this.onInputChange = this.onInputChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
// handle input change event
onInputChange(e) {
this.setState({ inputVal: e.target.value });
}
// handle button click event and pass data in parent
handleSubmit() {
this.props.handleInput(this.state.inputVal);
}
render() {
return (
<div>
<input value={this.state.inputVal} onChange={this.onInputChange} />
<input type='button' value='Submit' onClick={this.handleSubmit} />
</div>
);
}
}
export default Userinput;
Consider this.setState in the App Class. The first fetch() is writing your state like so:
this.setState({
lat: res.results[0].locations[0].latLng.lat,
long: res.results[0].locations[0].latLng.lng,
});
The second fetch() call sets a new state object, which removes the result from the first fetch() call, or whatever fetch() resolves faster:
.then((res) => {
console.log(res);
this.setState({ uvIndex: res });
})
You can fix this issue with object spread operator:
this.setState({...this.state, uvIndex: res });
This will keep a copy of your state, and only overwrite uvIndex (if it was set before)
Also consider the handleInputVal Method in App.js. I think same problem here. the state is overwritten with a new Object (setState doesn't update the state, it creates a new State), which means that lat and long are being set to undefined 🤔
handleInputValue(val) {
//this.setState({ inputValue: val }); sets lat and long to undefined
this.setState({...this.state, inputValue: val }); //keep lat and long values, and update inputValue with val
}

JSON Data Object Handling in React

Cant figure it out how to extract data from parsed and stored json object from state ... i have tried many ways but getting errors (data from openweathermap)
class App extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
data: null
};
}
getWeather(url) {
fetch(url)
.then(res => res.json())
.then(data =>
this.setState({
isLoading: false,
data: data
})
)
.catch(error => console.log("Error Loading data " + error));
}
componentDidMount() {
navigator.geolocation.getCurrentPosition(position => {
const url = `${API}lat=${position.coords.latitude}&lon=${position.coords.longitude}&${APPID}`;
this.getWeather(url);
});
}
render() {
return (
<div>
{console.log(this.state.data)}
{/*renders json*/}
<h1>{this.state.data.name}</h1>
</div>
);
}
}
I believe you might be getting cannot read property 'name' of null. Not sure about the exact error but something like that.
Try to use
<h1>{ this.state.data && this.state.data.name }</h1>
Till the time API is not giving the response, your data in the state is null so you cannot access data.name.

React Application 'npm test' Warning

I created a React application for practice. It works in my browser; however when I ran "npm test" from my terminal, it giving me a warning "Can only update a mounted or mounting component. This usually means you called setState, replaceState, or forceUpdate on an unmounted component. Please check the code for the FetchOne component."
I checked the FetchOne component but I couldn't figure it out. Is there anybody help me out, thank you!
Here is my code:
import React, {Component} from 'react';
const API = 'https://hn.algolia.com/api/v1/search?query=';
const DEFAULE_TITLE = 'N/A title';
class FetchOne extends Component{
constructor(){
super()
this.state=({
hits: [],
isLoading: false,
error: null,
})
}
componentDidMount(){
this.setState({ isLoading: true })
fetch(API)
.then(response =>{
if(response.ok){
return response.json()
}else{
throw Error('Somethig went wrong...')
}
} )
.then( data => this.setState({
hits: data.hits,
isLoading: false,
}))
.catch(error => this.setState({
error,
isLoading: false
}))
}
render(){
const { hits, isLoading, error } = this.state;
if(isLoading){
return <p> Loading... </p>
}
if(error){
return <p>{error.message}</p>
}
return(
<div>
{hits.map(data =>
<ul key={data.objectID}>
<li>Title:<a href={data.url}> {data.title || DEFAULE_TITLE}</a></li>
<li>Author: {data.author}</li>
<li>Points: {data.points}</li>
</ul>
)}
</div>
)
}
}
export default FetchOne
Click HERE for Error Massage Screenshot
I had a similar issue previously. The issue related to the fact that the component had unmounted but I was still making a setState call. This can happen when using async functions and passing a callback which includes setState to test if this is the case.
Create
componentWillUnmount() {
console.log("Fetch One Unmounted");
}
Then see if the setState is called after the message appears.
componentDidMount(){
this.setState({ isLoading: true })
fetch(API)
.then(response =>{
if(response.ok){
return response.json()
}else{
throw Error('Somethig went wrong...')
}
} )
.then( data => this.setState({
hits: data.hits,
isLoading: false,
}, () => {console.log("setState Called")}))
.catch(error => this.setState({
error,
isLoading: false
}))
}

ReactJS componentDidMount does not produce the value before rendering

I have the following code and getting the values through the api and set to the state variable but the view is rendered before setting the value to the state. So i could not display the value in my view. How could i change the code to work fine?
this.state = {
activeJobs: [],
isLoading: true
};
componentDidMount(){
axios.get(this.state.url+'/tables')
.then(response => {
// If request is good...
const isLoading = true,
activeJobs = response.data.activeJobs;
this.setState({ activeJobs });
})
.catch((error) => {
console.log('error ' + error);
});
}
render() {
console.log(this.state.activeJobs)
<p className="text">{!this.state.isLoading && this.state.activeJobs.count} Jobs</p>
}
The console i have given inside the render shows blank array. I also tried by changing the function componentDidMount() to componentWillMount() but getting the same result.
There is no way to ensure that an async request will complete before rendering. You can display proper messages in render to reflect the status of the request.
For example - before calling axios, set the state to 'in process' or 'loading', so that render will show an appropriate message. Then, when loading finished successfully or with an error, set the state appropriately to render an appropriate message in the error case, and the result otherwise.
If you can't render yet, then simply return null:
render() {
if (!this.state.activeJobs && !this.state.isLoading) {
return null;
}
return (
<div>
{ this.state.isLoading && <p className="text">Loading...</p> }
{ !this.state.isLoading && <p className="test">{ this.state.activeJobs.count } Jobs</p>
</div>
);
}
In order to set isLoading, set it before the HTTP call:
componentDidMount(){
this.setState({ isLoading: true });
axios.get(this.state.url+'/tables')
.then(response => {
// If request is good...
const activeJobs = response.data.activeJobs;
this.setState({ activeJobs, isLoading: false });
})
.catch((error) => {
console.log('error ' + error);
});
}

React Multiple Fetch from state

so I am trying to fetch my SWAPI json-server twice using the homeworld id to get the homeworld name but I just get "TypeError: Cannot read property 'name' of undefined". Im fairly new to React so if it looks messy im sorry! Thanks in advance!
import React, { Component } from 'react';
import './Card.css';
const person = personID =>
`http://localhost:3008/people/${personID}`
const picture = pictureID =>
`http://localhost:3008/${pictureID}`
// const planet = planetID =>
// `http://localhost:3008/planets/${planetID}`
class Card extends Component {
constructor(props){
super(props);
this.state ={
requestFailed: false,
person: 1,
planet: 4
}
}
componentDidMount(){
fetch(person(this.state.person))
.then(response => {
if(!response.ok) {
throw Error("Network Request Failed");
}
return response
})
.then(d => d.json())
.then(d => {
this.setState({
cardData: d
})
}, () => {
this.setState({
requestFailed: true
})
})
fetch(`http://localhost:3008/planets/${this.state.cardData.homeworld}`)
.then(data => data.json())
.then(data => {
this.setState({
homeData: data
})
})
}
render() {
if(this.state.requestFailed === true) return <p>Error please try
again!</p>
if(!this.state.cardData) return <p>Loading ...</p>
return (
<div className='card'>
<div className='card-content'>
<div className='card-name'>{this.state.cardData.name}</div>
<img src={picture(this.state.cardData.image)}
alt='profile'/>
<p>
<span>Birthday:</span>
<span>{this.state.cardData.birth_year}</span>
</p>
<p>
{/* Note that in order to get the homeworld's name, you have to get the planet name from a different endpoint than the people */}
<span>Homeworld:</span>
<span>{this.state.homeData.name}</span>
</p>
</div>
</div>
);
}
}
export default Card;
It looks like the first problem is that you're attempting to render homeData.name before it's loaded. You should probably have a loading check similar to the cardData loading check. Or you could just render nothing until it's loaded:
{this.state.homeData ?
<span>{this.state.homeData.name}</span> :
<span>Loading...</span>
}
The 2nd problem is that you're doing the second fetch of homeData at the same time as the first fetch. So this line:
fetch(`http://localhost:3008/planets/${this.state.cardData.homeworld}`)
Will always fail because the cardData is not loaded into the state yet when it runs.
What you should do is move it inside the response part of the first request:
fetch(person(this.state.person))
.then(response => {
if(!response.ok) {
throw Error("Network Request Failed");
}
return response
})
.then(d => d.json())
.then(d => {
this.setState({
cardData: d
})
fetch(`http://localhost:3008/planets/${d.homeworld}`)
.then(data => data.json())
.then(data => {
this.setState({
homeData: data
})
})
}, () => {
this.setState({
requestFailed: true
})
})
It would help to extract these into separate functions to clean it up a bit.

Resources