Hi I am new to reactjs and I am trying to build button with a function doing some calculation by Reactjs. The logic is, first I will get two lists from database by two functions. After these 2 functions return results and setState, the calculate function will continue and do its job. But somehow the state is not being updated and it will crash. How can I secure the state is being updated before to the calculate? Thanks a lot!
Code:
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
dividendList : [],
divisorList : [],
};
}
getDividend(){
var self = this;
axios.post(SERVER_NAME + 'api/getDividend', {})
.then(function(response){
let results = response.data;
console.log(results)
self.setState({ dividendList : results.data})
})
.catch(function(err){
console.log(err)
});
}
getDivisor(){
var self = this;
axios.post(SERVER_NAME + 'api/getDivisor', {})
.then(function(response){
let results = response.data;
console.log(results)
self.setState({ divisorList : results.data})
})
.catch(function(err){
console.log(err)
});
}
doCal = () => {
var self = this;
self.getDividend();
self.getDivisor();
const { dividendList , divisorList} = self.state;
# then will loop the list and do math
# but since the state is not update, both lists are empty []
}
Tried Promise;
getDivisor(){
var self = this;
return new Promise((resolve, reject) => {
axios.post(SERVER_NAME + 'api/draw/getDivisor', {})
.then(function(response){
resolve(response)
})
.catch(function(err){
resolve();
});
})
}
I think the issue here is self.getDividend(); and self.getDivisor(); are async operations. They will take some time to complete. By the time you hit the next line const { dividendList , divisorList} = self.state;, these operations are not complete and you will end up getting empty lists.
One way to address this is using moving your doCal function logic after getDividend and getDivisor are completed. You can also execute these in parallel instead of in a sequence. I used async format instead of .then(). It is just a sysntatic sugar. You can achieve the same using .then() if you prefer that way
async function doCalc() {
const prom1 = axios.get('https://..dividentList');
const prom2 = axios.get('https://..divisorList');
const results = await Promise.all([ prom1, prom2]); // wait for both promise to complete
// look inside results to get your data and set the state
// continue doCal logic
}
Using .then()
request1('/dividentList')
.then((res) => {
//setState for divident
return request2('/divisorList'); // this will return a promise to chain on
})
.then((res) => {
setState for divisor
return Promise.resolve('Success') // we send back a resolved promise to continue chaining
})
.then(() => {
doCalc logic
})
.catch((err) => {
console.log('something went wrong');
});
I looked at your code and thought it should be changed like this to be correct.
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
dividendList: [],
divisorList: [],
};
}
componentDidMount() {
// the API just need be called once, so put here
this.getDividend()
this.getDivisor()
}
componentDidUpdate(_, prevState) {
const { dividendList , divisorList } = this.state;
// Ensure that the answer is only calculated once
// the answer is only be calculated while the two list data are obtained
if (
prevState.divisorList.length === 0 &&
prevState.dividendList.length === 0 &&
divisorList.length > 0 &&
dividendList.length > 0
) {
doCal()
}
}
getDividend(){
var self = this;
axios.post(SERVER_NAME + 'api/getDividend', {})
.then(function(response){
let results = response.data;
console.log(results)
self.setState({ dividendList : results.data})
})
.catch(function(err){
console.log(err)
});
}
getDivisor(){
var self = this;
axios.post(SERVER_NAME + 'api/getDivisor', {})
.then(function(response){
let results = response.data;
console.log(results)
self.setState({ divisorList : results.data})
})
.catch(function(err){
console.log(err)
});
}
doCal = () => {
const { dividendList , divisorList } = this.state;
# then will loop the list and do math
# but since the state is not update, both lists are empty []
this.setState({ answer: 'xxx' })
}
render() {
const { dividendList, divisorList, answer } = this.state
if (dividendList.length === 0 && divisorList.length === 0) {
return <div>Loading...</div>
}
if (!answer) {
return <div>Error</div>
}
return <div>{answer}</div>
}
}
The following are just some suggestions to make the code easier to read,
you can use arrow function so that you don't need to write self.setState({...})
getDividend = () => {
axios.post(SERVER_NAME + 'api/getDivisor', {})
.then((response) => {
let results = response.data;
console.log(results)
this.setState({ divisorList : results.data})
})
.catch((err) => {
console.log(err)
});
}
and you can also use async/await instead of promise.then
getDividend = async () => {
const response = await axios.post(SERVER_NAME + 'api/getDivisor', {})
let results = response.data;
console.log(results)
this.setState({ divisorList : results.data})
}
Set 'dividendList' and 'divisorList' equals to 'null' by default. Then, when a function that uses those lists is called, make a if statement to verify if those states goes for false (if they are still null) then return inside the function, if not, it should not crash anything.
Related
handleSubmit = () => {
this.setState({ modalState: false })
this.state.codeToClass.forEach((code, classId, map) => {
const cr = _.find(this.state.classRoles, { id: classId })
if (code === cr.classCode) {
console.log('here')
this.setState(state => ({
classRoles: state.classRoles.map((cc) => {
console.log(cc.id)
console.log(classId)
console.log(cc.id === classId)
if (cc.id === classId) {
console.log('here1')
return {
...cc,
role: 'TA',
}
}
console.log('what')
return cc
}),
}), ()=> console.log(this.state.classRoles)) //this is called later
} else {
NotificationManager.error('Failed to register as TA.')
}
})
console.log(this.state.classRoles) //this is called first
this.state.classRoles.forEach((c) => {
if (c.role === '') {
api.deleteClassUser(c.id, this.state.user.id)
} else {
api.postAddClass(c.id, this.state.user.id, c.role)
console.log(c)
}
})
EventEmitter.publish('currentlyEnrolled', this.state.classRoles)
}
I'm trying to run the second forEach after the first forEach has finished,i.e. state has been updated. But it keeps running in this order. Is there a way to fix this?
Promise is your friend.
// You map your operations in to Promises.
const promises = this.state.codeToClass.map((code, classId, map) => {
return new Promise(resolve=>{
const cr = _.find(this.state.classRoles, { id: classId })
if (code === cr.classCode) {
console.log('here')
this.setState(state => ({
classRoles: state.classRoles.map((cc) => {
console.log(cc.id)
console.log(classId)
console.log(cc.id === classId)
if (cc.id === classId) {
console.log('here1')
return {
...cc,
role: 'TA',
}
}
console.log('what')
return cc
}),
}), ()=> resolve()) //this is called later
} else {
NotificationManager.error('Failed to register as TA.')
}
})
})
// and then you wait for all of the promises
await Promise.All(promises);
// then continue to execution
There are two options.
Use Promise
Async await
Since map can be used with await, I think
const tempState = this.state.codeToclass;
await tempState.map(...
This way can work :)
this.setState is an asynchronous operation.
You can try something like this:
handleSubmit = () => {
//some code...
this.setState(state => ({
state.codeToClass.forEach((...args) => {
//logic to update the state...
});
}), setClassRoles); //call a function after the state value has updated
}
setClassRoles = () => {
this.state.classRoles.forEach((...args) => {
//your code...
});
EventEmitter.publish('currentlyEnrolled', this.state.classRoles)
}
I need to get datas with nested foreach, but I can't fill my array.
At the end of this code I would like to have an array (segId) with my datas but it is empty (because of aynschronous).
I read that I had to use Promise.all but I can't beacause my promise are nested
I'm beginner so my code is far from perfect
How can I do that ?
async function getActivities(strava, accessToken)
{
const payload = await strava.athlete.listActivities({'access_token':accessToken, 'after':'1595281514', 'per_page':'10'})
return payload;
}
async function getActivity(strava, accessToken, id)
{
const payload = await strava.activities.get({'access_token':accessToken, 'id':id, 'include_all_efforts':'true'})
return payload;
}
async function getSegment(strava, accessToken, id)
{
const payload = await strava.segments.get({'access_token':accessToken,'id':id})
return payload
}
var tableau = []
var segId = []
const activities = getActivities(strava, accessToken)
activities.then(value => {
value.forEach((element, index) => {
const activity = getActivity(strava, accessToken, element['id'])
activity.then(value => {
value['segment_efforts'].forEach((element, index) => {
const segment = getSegment(strava, accessToken, element['segment']['id'])
segment.then(value => {
segId.push(value['id'])
})
//console.log(segId)
});
});
})
}) console.log(segId)
Regards
PS : Sorry for my english ...
Something like this should work. You need to always return the inner promises to include them in your promise chain. Consider splitting the code into functions to make it more readable.
getActivities(strava, accessToken).then(activities => {
return Promise.all(activities.map(elem => {
return getActivity(strava, accessToken, elem['id']).then(activity => {
return Promise.all(activity['segment_efforts'].map(elem => {
return getSegment(strava, accessToken, elem['segment']['id']).then(segment => {
segId.push(segment['id']);
});
}));
})
}));
})
.then(_ => {
console.log(segId);
});
I have a function that refreshes the data of my component when the function is called. At this moment it only works for one component at a time. But I want to refresh two components at once. This is my refresh function:
fetchDataByName = name => {
const { retrievedData } = this.state;
const { fetcher } = this.props;
const fetch = _.find(fetcher, { name });
if (typeof fetch === "undefined") {
throw new Error(`Fetch with ${name} cannot be found in fetcher`);
}
this.fetchData(fetch, (error, data) => {
retrievedData[name] = data;
this._isMounted && this.setState({ retrievedData });
});
};
My function is called like this:
refresh("meetingTypes");
As it it passed as props to my component:
return (
<Component
{...retrievedData}
{...componentProps}
refresh={this.fetchDataByName}
/>
);
I tried passing multiple component names as an array like this:
const args = ['meetingTypes', 'exampleMeetingTypes'];
refresh(args);
And then check in my fetchDataByName function if name is an array and loop through the array to fetch the data. But then the function is still executed after each other instead of at the same time. So my question is:
What would be the best way to implement this that it seems like the
function is executed at once instead of first refreshing meetingTypes
and then exampleMeetingTypes?
Should I use async/await or are there better options?
The fetchData function:
fetchData = (fetch, callback) => {
const { componentProps } = this.props;
let { route, params = [] } = fetch;
let fetchData = true;
// if fetcher url contains params and the param can be found
// in the component props, they should be replaced.
_.each(params, param => {
if (componentProps[param]) {
route = route.replace(`:${param}`, componentProps[param]);
} else {
fetchData = false; // don't fetch data for this entry as the params are not given
}
});
if (fetchData) {
axios
.get(route)
.then(({ data }) => {
if (this.isMounted) {
callback(null, data);
}
})
.catch(error => {
if (error.response.status == 403) {
this._isMounted && this.setState({ errorCode: 403 });
setMessage({
text: "Unauthorized",
type: "error"
});
}
if (error.response.status == 401) {
this._isMounted && this.setState({ errorCode: 401 });
window.location.href = "/login";
}
if (error.response.status != 403) {
console.error("Your backend is failing.", error);
}
callback(error, null);
});
} else {
callback(null, null);
}
};
I assume fetchData works asynchronously (ajax or similar). To refresh two aspects of the data in parallel, simply make two calls instead of one:
refresh("meetingTypes");
refresh("exampleMeetingTypes");
The two ajax calls or whatever will run in parallel, each updating the component when it finishes. But: See the "Side Note" below, there's a problem with fetchDataByName.
If you want to avoid updating the component twice, you'll have to update fetchDataByName to either accept multiple names or to return a promise of the result (or similar) rather than updating the component directly, so the caller can do multiple calls and wait for both results before doing the update.
Side note: This aspect of fetchDataByName looks suspect:
fetchDataByName = name => {
const { retrievedData } = this.state; // <=============================
const { fetcher } = this.props;
const fetch = _.find(fetcher, { name });
if (typeof fetch === "undefined") {
throw new Error(`Fetch with ${name} cannot be found in fetcher`);
}
this.fetchData(fetch, (error, data) => {
retrievedData[name] = data; // <=============================
this._isMounted && this.setState({ retrievedData });
});
};
Two problems with that:
It updates an object stored in your state directly, which is something you must never do with React.
It replaces the entire retrievedData object with one that may well be stale.
Instead:
fetchDataByName = name => {
// *** No `retrievedData` here
const { fetcher } = this.props;
const fetch = _.find(fetcher, { name });
if (typeof fetch === "undefined") {
throw new Error(`Fetch with ${name} cannot be found in fetcher`);
}
this.fetchData(fetch, (error, data) => {
if (this._isMounted) { // ***
this.setState(({retrievedData}) => ( // ***
{ retrievedData: {...retrievedData, [name]: data} } // ***
); // ***
} // ***
});
};
That removes the in-place mutation of the object with spread, and uses an up-to-date version of retrievedData by using the callback version of setState.
I make a request to the server via a map with different urls, then I set the data in State and use it for output. I want the requests to be consecutive but sometimes they do not work correctly and get bugs, how to write the code for normal data retrieval?
const urlList = ["countries", "states", "cities", "users"];
componentDidMount() {
urlList.map( (url, index) => {
return servicesAPI.getResourse(url).then( (body) => {
index !== 3 ? this.setState({
dataAPI : [...this.state.dataAPI, body] }) :
this.setState({
dataAPI : [...this.state.dataAPI, body],
loaded: true
})
})
})
export default class ServicesAPI {
_apiBase = `http://localhost:3001/`;
async getResourse(url) {
const res = await fetch(`${this._apiBase}${url}`);
if (!res.ok) {
throw new Error(`Could not fetch ${url}` +
`, received ${res.status}`)
}
return await res.json();
}
Use of Promise.all();
componentDidMount() {
const fetchPromises = [];
urlList.forEach( (url, index) => {
fetchPromises.push(servicesAPI.getResourse(url));
});
const allResourcesPromise = Promise.all(fetchPromises);
allResourcesPromise.then(data => {
// list with responses
}).catch(err => {
console.log(err.toString());
});
}
Sample example:
https://jsbin.com/vevufumano/1/edit?html,js,console,output
Also instead of then, where is possible, you can use async/await for more cleaner code.
I have a situation where I make three fetch calls. Every fetch calls has a callback function which will update the respective property of state.twitterfeed object and finally setState. Issue is that it is calling the setState 3 times as of now. My aim is to use promise.all and update setStatus only once. I tried multiple times but its confusing and challenging.
Code:
this.state = {
twitterfeed: {
techcrunch: [],
laughingsquid: [],
appdirect: []
}
}
updateTwitterFeed = (data, user) => {
const twitterfeed = { ...this.state.twitterfeed
};
if (user === "appdirect") {
twitterfeed.appdirect = data;
} else if (user === "laughingsquid") {
twitterfeed.laughingsquid = data;
} else {
twitterfeed.techcrunch = data;
}
this.setState({
isloadcomplete: true,
twitterfeed
});
};
componentDidMount() {
fetch(
"http://localhost:7890/1.1/statuses/user_timeline.json?count=30&screen_name=techcrunch"
)
.then(response => response.json())
.then(data => this.updateTwitterFeed(data, "techcrunch"));
fetch(
"http://localhost:7890/1.1/statuses/user_timeline.json?count=30&screen_name=laughingsquid"
)
.then(response => response.json())
.then(data => this.updateTwitterFeed(data, "laughingsquid"));
fetch(
"http://localhost:7890/1.1/statuses/user_timeline.json?count=30&screen_name=appdirect"
)
.then(response => response.json())
.then(data => this.updateTwitterFeed(data, "appdirect"));
}
You should have a look at the documentation: Promise.all()
Promise.all() actually preserves the order for its returned values.
Hence you could have:
const promises = [];
promises.push(fetch("http://localhost:7890/1.1/statuses/user_timeline.json?count =30&screen_name=techcrunch"));
promises.push(fetch("http://localhost:7890/1.1/statuses/user_timeline.json?count=30&screen_name=laughingsquid"));
promises.push(fetch("http://localhost:7890/1.1/statuses/user_timeline.json?count=30&screen_name=appdirect"));
// Execute all promises
Promise.all(promises).then(values => {
console.log(values);
const twitterfeed = { ...this.state.twitterfeed};
twitterfeed.techcrunch = json.parse(values[0]);
twitterfeed.laughingsquid = json.parse(values[1]);
twitterfeed.appdirect = json.parse(values[2]);
this.setState({
isloadcomplete: true,
twitterfeed
});
});
If you are familiar with the axios library.You can use there axios.all([]) calling method. As mentioned in there docs :
function A() {
return axios.get(url,[config]);
}
function B() {
return axios.get(url,[config]);
}
axios.all([A(), B()])
.then(axios.spread(function (result_A, result_B) {
// Both requests are now complete and you can setSate here.
}));
Github : https://github.com/axios/axios
var promise1 = fetch("http://localhost:7890/1.1/statuses/user_timeline.json?count =30&screen_name=techcrunch");
var promise2 = fetch("http://localhost:7890/1.1/statuses/user_timeline.json?count=30&screen_name=laughingsquid");
var promise3 =fetch("http://localhost:7890/1.1/statuses/user_timeline.json?count=30&screen_name=appdirect");
Promise.all([promise1, promise2, promise3]).then(function(values) {
console.log(values);
});
//You can now extend it as you want