setState not working with forloop in react function - reactjs

I am nesting two api calls inside componentDidMount, everything working fine, the only issue I have is the state do not update so I put some console logs to see what's going on
fetch reviews done!
analysis done!
false
analysis done!
false
analysis done!
false
As you can see the state of loaded never get updated and by the way no data show up on the application, I probably messed up the logic with this function but I can't figure this out.
componentDidMount = () => {
this.setState({ loading: true });
fetch(
"https://url-one.com",
{
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
"X-Spree-Token": "xxx"
}
}
)
.then(response => response.json())
.then(responseJson => {
console.log('fetch reviews done!')
this.setState(
{
list: responseJson.reviews,
},
() => {
var obj = this.state.list;
var data = [];
for (let i in obj) {
fetch(
"https://url-two.com",
{
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify({
api_key: "uuu",
data: obj[i].text
})
}
)
.then(response => response.json())
.then(responseJson => {
data.push({'review': obj[i].text, 'analysis': responseJson.results * 100});
});
this.setState({
data: data,
loaded: true,
loading: false,
});
console.log('analysis done!')
console.log(this.state.loaded)
}
}
);
});
}
Of course if I use a separate function to update the state it works!
show = () => {
this.setState({ loaded: true });
};

As you can see the state of loaded never get updated
Because the setState happens asynchronous (not immediately update), using console.log(this.state.loaded) like that won't work as expected, instead, you might use the setState callback as you did with the second fetch, like so:
this.setState(
{
data: data,
loaded: true,
loading: false
},
() => {
console.log('analysis done!');
console.log(this.state.loaded);
}
);
EDIT
I think we should use Promise.all() to make it work, like this:
componentDidMount() {
this.setState({ loading: true });
fetch('https://url-one.com', {
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'X-Spree-Token': 'xxx'
}
})
.then(response => response.json())
.then(responseJson => {
console.log('fetch reviews done!');
this.setState(
{
list: responseJson.reviews
},
() => {
var obj = this.state.list;
var data = [];
var fetchArr = [];
for (let i in obj) {
let promise = fetch('https://url-two.com', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
api_key: 'uuu',
data: obj[i].text
})
}).then(response => response.json());
fetchArr.push(promise); // make an array of promises
}
Promise.all(fetchArr).then(values => { //after all promises resolved, we'll receive an array of responseJson, we'll loop through it
values.forEach((responseJson, i) => { //for each responseJson, we push it to the data array
data.push({
review: obj[i].text,
analysis: responseJson.results * 100
});
});
// finally, we update state
this.setState(
{
data: data,
loaded: true,
loading: false
},
() => {
console.log('analysis done!');
console.log(this.state.loaded);
}
);
});
}
);
});
}

You have a couple conflicting processes here that need to get shored up. You don't want to modify state during async actions because internally you will fire off a re-render, so I'd suggest reorganizing a bit. The loaded state is not resolving correctly because you aren't waiting for your subsequent requests to finish. Here is an example:

Related

How do i set the state from one API call and use the data for URL in next API call?

I need the data from API call 1 to add to the URL of API call 2. The data from API 2 will go into the URL for API 3. I am setting the state on each Axios request and it is not working. Returning undefined
componentDidMount() {
// Get the IP adress of user
axios
.get('https://api.ipify.org?format=json')
.then(res => {
this.setState({
ip: res.data.ip
});
console.log(`IP : ${this.state.ip}`);
})
.catch(err => console.log(err));
// GET the coordinates of a location based on IP adress
axios
.get(
'https://geo.ipify.org/api/v1?apiKey=YOUR_API_KEY&ipAddress=24.8.227.87'
)
.then(res => {
this.setState({
latitude: res.data.location.lat,
longitude: res.data.location.lng
});
console.log(
`Latitude: ${this.state.latitude}. Longitude: ${this.state.longitude}`
);
})
.catch(err => console.log(err));
// Make the API call on page load
axios({
method: 'get',
url: `https://developers.zomato.com/api/v2.1/geocode?lat=39.6924553&lon=-105.0256318`,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Accept: 'application/json',
'user-key': 'USER_KEY'
}
})
.then(res => {
const restaurantsNearMe = res.data.nearby_restaurants;
this.setState({
restaurants: restaurantsNearMe
});
// Pick out a random retaurant from what the API returns
var randomRestaurant =
restaurantsNearMe[
Math.floor(Math.random() * restaurantsNearMe.length)
];
// Select only the data that you want
var finalResult = {
name: randomRestaurant.restaurant.name,
id: randomRestaurant.restaurant.id,
rating: randomRestaurant.restaurant.user_rating.aggregate_rating,
ratingColor: randomRestaurant.restaurant.user_rating.rating_color,
address: randomRestaurant.restaurant.location.address,
delivery: randomRestaurant.restaurant.is_delivering_now,
typeOfFood: randomRestaurant.restaurant.cuisines
};
this.setState({
restaurant: finalResult
});
console.log(this.state.restaurant);
})
.catch(err => console.log(err));
}
You need a callback in setState, and in that callback you need to call your second API and so on. Check this.
This is what you want,
axios
.get('https://api.ipify.org?format=json')
.then(res => {
this.setState({
ip: res.data.ip
}, () => {
// GET the coordinates of a location based on IP adress
axios
.get(
'https://geo.ipify.org/api/v1?apiKey=YOUR_API_KEY&ipAddress=24.8.227.87'
)
.then(res => {
this.setState({
latitude: res.data.location.lat,
longitude: res.data.location.lng
}, () => {
// Make the API call on page load
axios({
method: 'get',
url: `https://developers.zomato.com/api/v2.1/geocode?lat=39.6924553&lon=-105.0256318`,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Accept: 'application/json',
'user-key': 'USER_KEY'
}
})
.then(res => {
const restaurantsNearMe = res.data.nearby_restaurants;
this.setState({
restaurants: restaurantsNearMe
});
// Pick out a random retaurant from what the API returns
var randomRestaurant =
restaurantsNearMe[
Math.floor(Math.random() * restaurantsNearMe.length)
];
// Select only the data that you want
var finalResult = {
name: randomRestaurant.restaurant.name,
id: randomRestaurant.restaurant.id,
rating: randomRestaurant.restaurant.user_rating.aggregate_rating,
ratingColor: randomRestaurant.restaurant.user_rating.rating_color,
address: randomRestaurant.restaurant.location.address,
delivery: randomRestaurant.restaurant.is_delivering_now,
typeOfFood: randomRestaurant.restaurant.cuisines
};
this.setState({
restaurant: finalResult
});
console.log(this.state.restaurant);
})
.catch(err => console.log(err));
});
console.log(
`Latitude: ${this.state.latitude}. Longitude: ${this.state.longitude}`
);
})
.catch(err => console.log(err));
});
console.log(`IP : ${this.state.ip}`);
})
.catch(err => console.log(err));
one thing to keep in mind, this.setState is not synchronous. React batches multiple set state calls to improve render performance. That's why you might be seeing undefined in the console.log.
setState method takes a callback as second parameter.
this.setState(newState, callbalck)
so try to console log in the callback and give it a try.
I dont know how do you call API's but, try something like this:
In componentDidMount you can do this:
async componentDidMount(){
const resApiOne = await callFirstApi();
this.setState({resApiOne});
const resApiTwo = await callSecondApi(resApiOne);
this.setState({resApiTwo});
}

How can I prevent a componentDidMount from running, before my webservice call finishes

When app starts, SplashScreen comes. Behind splashScreen I want to get username and password from cache and then call webservice.
But when I get data from cache like AsyncStorage.getItem('myKey') which is in componentWillMount, it starts render. It doesn't let componentWillMount finish.
My Big problem is that componentDidMount starts before my controlallMeth methods finishes. Due to that, app crashes in that situation. How can I fix this issue?
Here is my code:
I get username and password from app cache and call webservice like this:
componentWillMount(){
AsyncStorage.getItem('myKey').then(value => {
let valuePrsed = JSON.parse(value);
if(valuePrsed.username != null && valuePrsed.password != null)
{
this.setState({username: valuePrsed.username, password: valuePrsed.password});
this.controlallMeth(); // call webservice
}
})
}
Here is method where I call webservice:
controlallMeth(){
let collection={}
collection.username = this.state.username,
collection.password = this.state.password
fetch('url', {
method: 'POST',
headers: new Headers({
Accept: 'application/json',
'Content-Type': 'application/json', // <-- Specifying the Content-Type
}),
body: JSON.stringify(collection) // <-- Post parameters
})
.then((response) => response.text())
.then(leaders => {
this.setState({PersonalInfo: leaders});
})
.catch((error) => {
console.error(error);
});
}
And here is componentDidMount
componentDidMount() {
StatusBar.setHidden(true);
this.setState({ fadeAnim: new Animated.Value(0) },
() => {
Animated.timing( // Animate over time
this.state.fadeAnim, // The animated value to drive
{
toValue: 1, // Animate to opacity: 1 (opaque)
duration: 3000,
}
).start(() => {
if(this.state.values != null)
{
console.log("go page1");
this.props.navigation.navigate('Page1',{datas: this.state.PersonalInfo});
}
else{
this.props.navigation.navigate('login');
}
})
}) // Starts the animation
}
Given that the componentWillMount is deprecated now, it's better not to use it. You can get your cached values inside constructor or componentDidMount method
constructor(props) {
super(props);
StatusBar.setHidden(true);
AsyncStorage.getItem('myKey').then(value => {
let valuePrsed = JSON.parse(value);
if(valuePrsed.username != null && valuePrsed.password != null)
{
this.setState({username: valuePrsed.username, password: valuePrsed.password});
this.controlAllMeth(); // call webservice
}
});
}
For making sure that the animateing and fetching method completely finish before going to other screen, you can use Promise.all:
controlAllMeth() {
Promise.all([this.callFetch(), this.startAnim()])
.then(([fetchResponse, animResponse]) => {
this.setState({PersonalInfo: fetchResponse.text()});
if(this.state.values != null)
{
console.log("go page1");
this.props.navigation.navigate('Page1',{datas: this.state.PersonalInfo});
}
else{
this.props.navigation.navigate('login');
}
})
.catch(err => {
});
}
callFetch() {
let collection={}
collection.username = this.state.username,
collection.password = this.state.password
return fetch(url, {
method: 'POST',
headers: new Headers({
Accept: 'application/json',
'Content-Type': 'application/json', // <-- Specifying the Content-Type
}),
body: JSON.stringify(collection) // <-- Post parameters
}
);
}
startAnim() {
return new Promise((resolve, reject) => {
this.setState({ fadeAnim: new Animated.Value(0) },
() => {
Animated.timing( // Animate over time
this.state.fadeAnim, // The animated value to drive
{
toValue: 1, // Animate to opacity: 1 (opaque)
duration: 3000,
}
).start(() => {
resolve();
})
}); // Starts the animation
});
}
You might wanna use async method here, as controlallMeth an async call. Making the whole process to wait making sure you do the fetch request then move on.
async componentWillMount(){
AsyncStorage.getItem('myKey').then(value => {
let valuePrsed = JSON.parse(value);
if(valuePrsed.username != null && valuePrsed.password != null)
{
this.setState({username: valuePrsed.username, password: valuePrsed.password});
await this.controlallMeth(); //I am assuming you did a typo here this.controlall();
}
})
}
controlallMeth = async() => {
let collection={}
collection.username = this.state.username,
collection.password = this.state.password
const res = await fetch('url', {
method: 'POST',
headers: new Headers({
Accept: 'application/json',
'Content-Type': 'application/json', // <-- Specifying the Content-Type
}),
body: JSON.stringify(collection) // <-- Post parameters
})
const leaders = await res.text();
this.setState({PersonalInfo: leaders});
}
Though it's not recommended to have async calls in componentWillMount so you might wanan switch to componentDidMount

React native fetch url endcoded

Does anyone know what the problem is with the below code? Because currently i can get only 1 data from the db but when the db has 2 data or 3 data then it will show the following error message:
JSON Parse error : Unable to parse JSON String
componentDidMount(){
const packid = 'this.props.navigation.getParam'('packid');
return fetch('http://xxx.xxx.x.xxx/User_Project/packages_List_klcc.php?packid='+packid, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
}
}).then((response) => response.json())
.then((responseJson) => {
console.log(responseJson)
this.setState({
isLoading: false,
dataklcc: responseJson
}, function() {
// In this block you can do something with new state.
});
})
.catch((error) => {
console.error(error);
});
}

React Native refreshing data

I'm trying to refresh some data when users re-vistis the screen. The way im using other places and it works. but can't figure out why this won't fly on this screen?
componentDidMount = () => {
this.props.navigation.addListener('didFocus', this.handleDidFocus)
}
async handleDidFocus() {
...
}
This is how I load data the first time and want to load it again when users revisits.
componentWillMount() {
this.getGroupAccepted();
}
async getGroupAccepted() {
if (this.state.token == null) {
var token = await AsyncStorage.getItem("token");
this.setState({ "token": token });
}
fetch('https://.../api/group/getActive', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
token: this.state.token
})
})
.then(response => response.json())
.then((data) => {
this.setState({
groups_accepted: data.groups_active,
loading: false,
});
})
.catch((error) => {
console.error(error);
});
}
This is what worked. Now when a user revisits the screen it loads the data once again.
componentDidMount = () => {
this.props.navigation.addListener('didFocus', this._handleDataChange)
}
_handleDataChange = () => {
this.getGroupAccepted();
}

Cannot setState - tried with Axios, AJAX and fetch()

bit of React noob here, and this one is escaping me, so thanks in advance for what is very likely an easy fix.
All I want to do is set the state of my courses component to take in the array from my API. I'm accessing the data fine, and it's showing me the array of four objects in the console, but the state simply won't change. What am I missing?
And before anyone asks me why I'm not using Redux it's because I want to understand the fundamentals first.
import React, { Component } from 'react';
// import axios from 'axios';
import $ from 'jquery';
class CourseIndex extends Component {
constructor(){
super();
this.state = {
courses: []
}
}
componentWillMount(){
this.getCourses();
}
componentDidMount(){
this.getCourses();
}
getCourses(){
// ============================ jquery()================================
$.ajax(({
url:'/courses',
headers: {
"Authorization": localStorage.getItem('id_token')
},
dataType: 'json',
cache: false,
success: function(data){
this.setState({
courses: data
})
}.bind(this),
error: function(xhr, status, err){
console.log(err);
}
}))
console.log(this.state);
// ============================ fetch()================================
// fetch(
// '/courses',
// {
// headers: {
// "Authorization": localStorage.getItem('id_token')
// }
// })
// .then((res) => res.json())
// .then((data) => {
// this.setState({
// courses: data
// },console.log(this.state))
// });
// ============================ axios()================================
// axios
// .get(
// '/courses',
// {
// headers: {
// "Authorization": localStorage.getItem('id_token')
// }
// }
// )
// // .then(res => console.log(res.data))
// .then(res =>
// this.setState({
// courses: res
// }, console.log(this.state))
// )
// .catch(err => console.log(err));
// console.log(this.state.courses);
// const items = this.courses.map(res => (
// <li className="list-group-item">
// <h3>{ res.name }</h3>
// </li>
// ))
}
render () {
return (
<div>
</div>
)
}
}
export default CourseIndex;
Sorry for the commented out code, by the way - I was just experimenting with the different calling modules.
Thanks
$.ajax is asynchronous, so you cannot log the state before the request has finished and expect the state to have changed. setState itself is also asynchronous, so if you want to log the state after it has been changed, you can use the second argument to setState which is a callback function. You are currently invoking console.log straight away, but you want to give a function that will be invoked.
$.ajax({
url: "/courses",
headers: {
Authorization: localStorage.getItem("id_token")
},
dataType: "json",
cache: false,
success: function(data) {
this.setState(
{
courses: data
},
() => console.log(this.state)
);
}.bind(this),
error: function(xhr, status, err) {
console.log(err);
}
});

Resources