I am having this issue with setState in the code.
Trying to do
what I wanted to achieve is to concatenate results from all the API calls into one variable in State.Should I assign everything in the first API (as in second or third API call wrap within the first API .then(function) ?)
or
should i define each api separately
var requestDigAPI = ...
var requestNEWSAPI =...
and call
this.setstate({
this.state.articles.concat(dig,buzzfeed)
})
What is the right approach?
ERRORs
No matter the method react logs error.
If I set the State in other API within the first API returns
error buzzfeed unidentified
or setState outside of both API
error dig , buzzfeed unidentified
componentDidMount() {
this.setState({loading: true})
var apiRequestDig = fetch("api").then(function(response) {
return response.json()
});
var apiRequestNews = fetch("api").then(function(response) {
return response.json()
})
var apiREquestBuzzFeed = fetch(api).then(function(response) {
return response.json()
})
var combinedData = {
"apiRequestDig": {},
"apiRequestNews": {},
"apiREquestBuzzFeed": {}
};
Promise.all([apiRequestDig, apiRequestNews, apiREquestBuzzFeed]).then(function(values) {
combinedData["apiRequestDig"] = values[0];
combinedData["apiRequestNews"] = values[1];
combinedData["apiREquestBuzzFeed"] = values[2];
return combinedData;
});
var dig = apiRequestDig.then(results => {
let dig = results.data.feed.map(article => {
return {
title: article.content.title_alt,
image: article.content.media.images[0].url,
category: article.content.tags[0].name,
count: article.digg_score,
description: article.content.description,
url: article.content.url
}
})
apiREquestBuzzFeed.then(results => {
console.log(results.big_stories[0].title)
let buzzfeed = results.big_stories.map(article => {
return {
title: article.title,
image: article.images.small,
category: article.category,
count: article.impressions,
description: article.description,
url: "https://www.buzzfeed.com"+article.canonical_path
}
})
})
this.setState({
articles: this.state.articles.concat(dig),
loading: "none"
})
// console.log(this.state);
})
}
thanks for the advice
You could chain your API calls, but Promise.all() allows you to make concurrent calls, so why not use it?
However, I think your API functions should be defined outside of componentDidMount, for more readability and reusability:
/* Outside of your component */
const apiRequest = url => fetch(url).then(response => response.json())
const apiRequestDig = () => {
return apiRequest("https://dig/api/url").then(results => {
return results.data.feed.map(article => {
return {
title: article.content.title_alt
/* ... */
};
});
});
};
const apiRequestNews = () => {
return apiRequest("https://news/api/url").then(results => {
return results.big_stories.map(article => {
return {
title: article.title
/* ... */
};
});
});
};
const apiRequestBuzzFeed = () => {
return apiRequest("https://buzzfeed/api/url").then(results => {
return results.big_stories.map(article => {
return {
title: article.title
/* ... */
};
});
});
};
/* Inside your component */
componentDidMount() {
this.setState({loading: true});
Promise.all([
apiRequestDig(),
apiRequestNews(),
apiRequestBuzzFeed()
]).then(values => {
return values[0].concat(values[1], values[2]);
}).then(results => {
this.setState({
articles: this.state.articles.concat(results),
loading: "none"
});
}).catch(err => {
console.log('Oops, something went wrong', err);
});
}
How about moving state manipulation code within the resolve callback of Promise.all ?
componentDidMount() {
this.setState({loading: true})
const apiRequestDig = fetch("api").then(response => response.json());
const apiRequestNews = fetch("api").then(response => response.json());
const apiREquestBuzzFeed = fetch("api").then(response => response.json());
Promise.all([
apiRequestDig,
apiRequestNews,
apiREquestBuzzFeed
]).then(([dig, news, feed]) => {
const digs = dig.data.feed.map(article => ({
title: article.content.title_alt,
image: article.content.media.images[0].url,
category: article.content.tags[0].name,
count: article.digg_score,
description: article.content.description,
url: article.content.url
}));
const buzzfeed = feed.big_stories.map(article => ({
title: article.title,
image: article.images.small,
category: article.category,
count: article.impressions,
description: article.description,
url: `https://www.buzzfeed.com${article.canonical_path}`
}));
this.setState({
articles: [...this.state.articles, ...digs],
loading: "none"
});
// return anything you want as wrapped with promise
return {
apiRequestDig: dig,
apiRequestNews: news,
apiREquestBuzzFeed: feed
};
});
.catch(e => {
// catch your error here
})
}
Using Promise.all will be the best approach.But keep in mind about the fail fast behavior of Promise.all where in if one request fails then Promise.all will reject immediately MDN Link. This behavior can be mitigated by catching the error and resolving with empty data.
function makeAPICall(postId) {
return fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`).then(res => res.json());
}
var Posts = React.createClass({
getInitialState: function() {
return {};
},
componentDidMount: function() {
var requestsArray = [makeAPICall(1), makeAPICall(2), makeAPICall(3)];
Promise.all(requestsArray).then(values => {
var postTitles = values.map(post => post.title).join(", ");
this.setState({
data: postTitles
});
}).catch(console.error.bind(console));
},
render: function() {
return <div>{this.state.data}</div>;
}
});
ReactDOM.render(
<Posts/>,
document.getElementById('root')
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
Related
I have a function that fires onClick and is supposed to save an array of states to Firebase. There are two states that can fail due to async, so I tried to chain it all in Promises. The Promises are definitely resolving correctly, but the useState hook didn't update the state in resolve when I tried it. So I tried to chain it in the manner below and the same error (Function CollectionReference.doc() cannot be called with an empty path.) is still returning. Why would my states for mid and lastUpdated be empty?
const db = firebaseApp.firestore();
const initialFieldValues = {
mid: "",
lastUpdated: "",
};
const [store, setStore] = useState(initialFieldValues);
const createMID = () => {
return new Promise((resolve, reject) => {
let docID = db.collection("merchant").doc().id;
let key = "mid-".concat(docID);
if (key) {
resolve(key);
} else {
reject("failed to create mid key");
}
});
};
const getTimestamp = () => {
return new Promise((resolve, reject) => {
let timestamp = firebase.firestore.FieldValue.serverTimestamp();
if (timestamp) {
resolve(timestamp);
} else {
reject("failed to get timestamp");
}
});
};
const registerMerchant = () => {
createMID()
.then(
(data) => {
setStore({
...store,
mid: data,
});
console.log(data); //correctly returns data
console.log(store.mid); //returns empty; setStore isn't setting the state
return getTimestamp();
}).then(
(data2) => {
setStore({
...store,
lastUpdated: data2,
})
console.log(data2); //returns object
console.log(store.mid); //returns empty
}
).then(
() => {
if(!merchant && authenticated){
dispatch({
type: "SET_MERCHANT",
merchant: store,
});
} else if(merchant) {
return
}else{
dispatch({
type: "SET_MERCHANT",
merchant: null,
});
}
}
).then( ()=> {
// e.preventDefault();
db.collection('merchant').doc(store.mid).set(store);
}).catch(error => alert(error.message))
}
constructor() {
super();
this.state = {
detailWallet: [],
user: [],
historia: [],
namaPertama: [],
namaKirim: [],
}
componentDidMount() {
this.getDetailAPI();
this.getDetailPlaceAPI();
this.getHistoria();
}
getNameAPI = () => {
const id = this.props.match.params.id;
return new Promise(resolve => {
axios.get(`/wallet/user/${id}`)
.then((result) => {
resolve(
this.setState({
namaPertama: result.data.data.content[0].user.firstName
})
)
})
});
}
getHistoria = () => {
console.log(this.getNameAPI())
Promise.all([this.getNameAPI()])
.then(([result]) => {
this.setState({
namaKirim: result
})
})
axios.get(`/wallet/type/6?q=`)
.then((result) => {
this.setState({
historia: result.data.data.content
})
})
}
so i make a function that have a method get inside a promise, and when i console.log it in getHistoria, the promise give me an empty array..
can someone tell me what's wrong with my code? i just learning about the promise so i still don't really know about it, thank you..
You are returning the result of setState, I don't think setState is returning anything. Besides, setState is asynchronous, so you won't have the result of the updated state if you loged the state just after.
Just set the state with setState and return some value.
EDIT:
getNameAPI = () => {
const id = this.props.match.params.id;
return new Promise(resolve => {
axios.get(`/wallet/user/${id}`)
.then((result) => {
let value = result.data.data.content[0].user.firstName;
this.setState({ namaPertama: value})
resolve(value)
})
});
}
New to react-native and at the moment I'm working on chaining promises.
myFunction(human, destination = null) {
const { navigation } = this.props;
const { onRefresh } = navigation.state.params;
this.setState({ isLoading: true });
return PeopleService.closeService(
human.humanId,
destinationPoint && destinationPoint.humanId,
)
.then((result) => {
if (result) {
PeopleHelperService.refreshInfo().then(() => {
if (onRefresh) {
onRefresh();
}
navigation.popToTop();
PopUp.showSuccess(
"Success message",
);
});
}
PopUp.showError(
"Failing message",
);
return null;
})
.finally(() => this.setState({ isLoading: false }));
}
Things that I want to achieve is removing chain responsibility and make it simple without chaining.
Could anyone guide me on how can I achieve this? Links to some documentation and other source will be very helpful for me to understand how to make it.
UPDATE:
Seems to be the answer with async/await working.
If you don't want using promise then using async await. Here it is.
myFunction = async (human, destination = null) => {
const { navigation } = this.props;
const { onRefresh } = navigation.state.params;
this.setState({ isLoading: true });
let result = await PeopleService.closeService(
human.humanId,
destinationPoint && destinationPoint.humanId,
);
if (result) {
await PeopleHelperService.refreshInfo();
if (onRefresh) {
onRefresh();
}
navigation.popToTop();
PopUp.showSuccess(
"Success message",
);
}
PopUp.showError(
"Failing message",
);
this.setState({ isLoading: false })
}
So i have this method in container component:
getProfilesOptions = () => {
const result = firebase.firestore().collection('roles').get().then(snapshot => {
const options = []
snapshot.docs.forEach(doc => {
options.push({ value: doc.id, label: doc.data().profile })
//console.log(doc.id) - everything ok, i'm fetching data correctyly
});
return options
})
console.log(result)//here i have pending result in debugger
return result
}
then, i'm passing link into child...child...child component.
Then i some child i want to call it, and get array as a result, and then set the state:
componentDidUpdate() {
if(this.state.isFocused && !this.state.options){
const options = this.props.getOptions()
this.setState({
options: options
})
}
}
Can i have a solution of this problem? Ofcourse i can pass props as result instead of props ref to the method, but can i use the method? How to improve getProfilesOptions?
You should wrap your firebase call in a Promise, because this is an async call.
getProfilesOptions = () => {
return new Promise(resolve => {
firebase.firestore().collection('roles').get().then(snapshot => {
const options = []
snapshot.docs.forEach(doc => {
options.push({ value: doc.id, label: doc.data().profile })
//console.log(doc.id) - everything ok, i'm fetching data correctyly
});
resolve(options)
})
}
}
And get the result in your component with .then()
componentDidUpdate() {
if(this.state.isFocused && !this.state.options){
this.props.getOptions().then(options => {
this.setState({
options: options
})
})
}
}
You can read more about Javascript Promises here
I want to check that a piece of code is being called, so I'm using a sinon spy to assert this. However, the spy seems to be failing, despite console.logs showing that the code has been called correctly.
I'm wondering if my function being a generator is causing my spy to misreport what it's doing.
my code (i've taken out some chunks for brevity):
isBlacklisted(release, jobUUID) {
names.forEach((name) => {
this._spawnPythonProcessGenerator(
this.IS_BLACKLISTED_SCRIPT,
name
).next().value
.then((data) => {
console.log(data);
})
.catch((err) => {
this._errorEvent(release, name, err, jobUUID);
});
}, this);
}
_errorEvent(release, name, err, jobUUID) {
console.log('got here');
}
*_spawnPythonProcessGenerator(scriptSrc, name) {
const pythonProcess = this._childProcess.spawn(
'python3',
[...arguments]
);
yield new Promise((resolve, reject) => {
pythonProcess.stderr.on('data', (err) => {
reject(err.toString());
});
pythonProcess.stdout.on('data', (data) => {
resolve(data.toString());
});
});
}
and my tests:
const Blacklist = require('../../src/Blacklist2');
const childProcess = require('child_process');
const uuid = require('uuid/v4');
describe('Blacklist', () => {
let blacklist;
beforeEach(() => {
blacklist = new Blacklist(childProcess);
blacklist.IS_BLACKLISTED_SCRIPT = './test/helpers/good.py';
});
describe('isBlacklisted', () => {
it('should call the _errorEvent for every name in a release when the blacklist application is not available', async () => {
let release = {
id: 1001,
asset_controller: {
id: 54321,
},
display_name: 'Blah',
names: [
{
id: 2001,
name: 'Blah',
},
],
};
blacklist.IS_BLACKLISTED_SCRIPT = './test/helpers/'+ uuid() +'.py';
const spy = sinon.spy(blacklist, '_errorEvent');
blacklist.isBlacklisted(release, uuid());
console.log(spy);
sinon.assert.calledTwice(spy);
spy.restore();
});
});
});
my spy reports:
notCalled: true
I'll expand my comment into an actual answer, hopefully that helps.
Your problem lies with asynchrony, not with the generator. You need isBlacklisted to return a promise you can wait on. Otherwise your assertion happens before the spy is called.
Something like this:
isBlacklisted(release, jobUUID) {
let promises = names.map((name) => {
return this._spawnPythonProcessGenerator(
this.IS_BLACKLISTED_SCRIPT,
name
).next().value
.then((data) => {
console.log(data);
})
.catch((err) => {
this._errorEvent(release, name, err, jobUUID);
});
}, this);
return Promise.all(promises);
}
Then, in your test:
return blacklist.isBlacklisted(release, uuid())
.then(() => {
sinon.assert.calledTwice(spy);
});
Also... This isn't related to your problem, but your _spawnPythonProcessGenerator method doesn't need to be a generator. You're only using the first value of it by calling next like that and calling the whole thing over again for each array item.
It will work the same if you take out the *, change yield to return, and skip the .next().value when you call it. You also probably want to rename it because it's not a generator.
_spawnPythonProcess(scriptSrc, name) {
const pythonProcess = this._childProcess.spawn(
'python3',
[...arguments]
);
return new Promise((resolve, reject) => {
pythonProcess.stderr.on('data', (err) => {
reject(err.toString());
});
pythonProcess.stdout.on('data', (data) => {
resolve(data.toString());
});
});
}
When you call it:
let promises = names.map((name) => {
return this._spawnPythonProcess(
this.IS_BLACKLISTED_SCRIPT,
name
)
.then((data) => {
console.log(data);
})
.catch((err) => {
this._errorEvent(release, name, err, jobUUID);
});
}, this);
return Promise.all(promises);