Nested fetch/then methods - reactjs

I am using the flickr API to search images and I would like to get photos with theirs tags at same time.
To do it, I need first to use flickr.photos.search method to fetch the photo_id and build the photo url (1st and 2nd 'then' methods). In the 3th 'then' part I used another API method flickr.photos.getInfo to get the tags for each photo and finally return urlPhoto and tagsInfo like json.
The problem is that tagsInfo variable continues been a promise and I can not render the tags (array) of the photo. However, urlPhoto has a correct value.
export function fetchAll(...) {
return fetch(BASE_URL + encodeGetParams(params1), options)
.then(response => {
return response.json();
})
.then((data) => {
return data.photos.photo.map(e =>
({
"photo_id": e.id,
"urlPhoto": 'https://farm'+e.farm+'.staticflickr.com/'+e.server+'/'+e.id+'_'+e.secret+'.jpg',
})
)
})
.then((data) => {
return data.map(e => {
const url = BASE_URL + encodeGetParams({ ...params2, "photo_id": e.photo_id });
const tagsInfo = fetch(url, options)
.then(data => data.json())
.then(data => data.photo.tags.tag.map(e => e._content));
return {
"urlPhoto": e.urlPhoto,
"tagsInfo": tagsInfo
}
}
)
})
}

You could create a separate promise for each element in the array, use Promise.all on those promises and return that.
export function fetchAll(/* ... */) {
return fetch(BASE_URL + encodeGetParams(params1), options)
.then(res => res.json())
.then(data => {
const promises = data.photos.photo.map(e => {
const result = {
urlPhoto: `https://farm${e.farm}.staticflickr.com/${e.server}/${e.id}_${e.secret}.jpg`
};
const url = BASE_URL + encodeGetParams({ ...params2, photo_id: e.photo_id });
return fetch(url, options)
.then(res => res.json())
.then(data => {
result.tagsInfo = data.photo.tags.tag.map(e => e._content);
return result;
});
});
return Promise.all(promises);
});
}

Do you not just need to return the last fetch and add an extra .then that would resolve to
{
"urlPhoto": e.urlPhoto,
"tagsInfo": tagsInfo
}
like
export function fetchAll(...) {
return fetch(BASE_URL + encodeGetParams(params1), options)
.then(response => {
return response.json();
})
.then((data) => {
return data.photos.photo.map(e =>
({
"photo_id": e.id,
"urlPhoto": 'https://farm'+e.farm+'.staticflickr.com/'+e.server+'/'+e.id+'_'+e.secret+'.jpg',
})
)
})
.then((data) => {
return data.map(e => {
const url = BASE_URL + encodeGetParams({ ...params2, "photo_id": e.photo_id });
return fetch(url, options)
.then(data => data.json())
.then(data => data.photo.tags.tag.map(e => e._content))
.then(tagInfo => {
return {
"urlPhoto": e.urlPhoto
"tagsInfo": tagsInfo
}
})
}
)
})
}
What you're currently doing is returning the urlPhoto/tagsInfo before the tagsInfo fetch promise has resolved so an extra then should fix it!

Related

call function synchronously in reactjs

I want to call function only after previous function gets executed. I tried with promises but its not working,I also tried with async await but the last function is getting executed.After execution of first function its state value i want to pass to next function and so on.Please help me in this.Thanks in advance.
handleAllFunctionsOnClickPayLater() {
let promise = Promise.resolve();
promise
.then(() => this.handleGuestLogin())
.then(() => setTimeout(this.handleAddress(),1000))
.then(() => setTimeout(this.handlePayLater(),2000))
}
handleGuestLogin() {
const UserDetails = {
name: this.state.name,
email: this.state.email,
mobile: this.state.number
}
fetch(api,{
method : 'POST',
body: JSON.stringify(UserDetails)
})
.then(res => res.json())
.then(data => {
return this.setState({
cid: data.Data.cid
},() => {console.log(this.state.cid)})
})
}
handleAddress() {
var address_details = {
cid:this.state.cid
...other details
}
fetch(api,{
method : 'POST',
body: JSON.stringify(address_details)
})
.then(res => res.json())
.then(data => {
console.log("address added in db customer_address",data);
return this.setState({
address_id: data.address_id,
})
}
handlePayLater = () => {
var bookingDetails = {
cid: this.state.cid,
address_id: this.state.address_id
}
fetch(api,{
method : 'POST',
body : JSON.stringify(bookingDetails)
})
.then(res => res.json())
.then(data => {
return this.setState({bookingId:data.booking_id});
}
Assuming handleAddress, handleGuestLogin and handlePayLater return promises, you can use an async/await function
synchronousPromises = async () => {
try {
const handleGuestLoginResult = await this.handleGuestLogin();
const handleAddressResult = await this.handleAddress();
const handlePayLaterResult = await this.handlePayLater();
} catch (error)
{
return reject(error); //will cause .catch to fire
}
return resolve([
handleGuestLoginResult,
handleAddressResult,
handlePayLaterResult
]); //will cause .then to fire
}
since synchronousPromises is an async function, it itself returns a promise. to use it, you can call it as
callSyncronousPromises = () => {
synchronousPromises()
.then(success => {
//handle success
})
.catch(error => {
//handle error
}
}

How can I dynamically rerender my api to my webpage?

So I have this api and I am making a get request in my ComponentDidMount() to dynamically render it to my page and it works. The issue I am facing is when I make a post request to add items to the list, it does not show on my webpage unless I refresh it. The backend is my data.json so I don't know if that is the problem but essentially when I make a post request, I am adding data to my data.json and I want that to rerender on my page without me refreshing it.
componentDidMount() {
axios.get("/api/workboard")
.then(res => {
res.data["boardLists"].map((item, key) => {
// console.log(Object.keys(item)[0])
this.setState(prevState => ({
data: [...prevState.data, item],
titles: [...prevState.titles, Object.keys(item)[0]]
}))
})
// console.log(this.state.titles)
// console.log(this.state.data)
}).catch(err => console.log(err))
}
addListItemHandler = () => {
axios({
method: 'post',
url: 'api/workboard/0/list',
data: {
title: "Untitled" ,
description: "No Description"
}
})
.then(res => {
console.log(res)
})
.catch(err => console.log(err));
}
render() {
let board = this.state.data.map((item, key) => {
return <WorkBoardContainer
key={key}
title={item[this.state.titles[key]]["title"]}
listItems={item[this.state.titles[key]]["list"].map((i) => {
return i["title"]
})}
/>
})
return (
<div className={classes.App}>
<AddButton addListItemHandler={this.addListItemHandler}/>
{board}
</div>
);
}
Try moving the fetching part as a seperate function and call it again once the post request is done.
componentDidMount() {
// fetch data when component is mounted
this.fetchData();
}
fetchData = () => {
axios.get("/api/workboard")
.then(res => {
res.data["boardLists"].map((item, key) => {
this.setState(prevState => ({
data: [...prevState.data, item],
titles: [...prevState.titles, Object.keys(item)[0]]
}))
})
}).catch(err => console.log(err))
}
addListItemHandler = () => {
axios({
method: 'post',
url: 'api/workboard/0/list',
data: {
title: "Untitled" ,
description: "No Description"
}
})
.then(res => {
console.log(res);
// fetch data again once post is done.
this.fetchData();
})
.catch(err => console.log(err));
}

Axios get inside a .then is not working as intended

I am using axios to make a http request. Inside the .then() i am using another axios call. Finally, I have a third then() which is supposed to run after the second then but it is actually not doing so.
let assets = [];
export const initAssets = () => {
return dispatch => {
dispatch(setLoadingToTrue());
axios.get('https://....json')
.then(response => {
for(let key in response.data) {
assets.push(response.data[key]);
}
})
.then(() => {
const token = '...';
assets.forEach((cur) => {
axios.get('...' + cur.ticker + '/quote?token=' + token)
.then(response => {
console.log(response);
cur.price = response.data.latestPrice;
})
})
})
.then(() => {
dispatch(initAllAssets(assets));
dispatch(setLoadingToFalse());
})
.catch(error => {
console.log(error);
dispatch(setLoadingToFalse());
})
}
}
The dispatch on the last .then are being executed before the axios request in the second then is completed. How can I make the last then run only after the second axios request is completed?
You can make use of Promise.all to return the response to next .then block and it will be called once all the promises have resolved in the second .then
let assets = [];
export const initAssets = () => {
return dispatch => {
dispatch(setLoadingToTrue());
axios.get('https://....json')
.then(response => {
for(let key in response.data) {
assets.push(response.data[key]);
}
})
.then(() => {
const token = '...';
const promises = [];
assets.forEach((cur) => {
promises.push(axios.get('...' + cur.ticker + '/quote?token=' + token)
.then(response => {
console.log(response);
cur.price = response.data.latestPrice;
}))
})
return Promise.all(promises);
})
.then(() => {
dispatch(initAllAssets(assets));
dispatch(setLoadingToFalse());
})
.catch(error => {
console.log(error);
dispatch(setLoadingToFalse());
})
}
}

problem with fetch in componentDidMount()

my list of users is undefined when i try to console.log it.
Maybe i didn't get something ?
I'd like to get my list of users from my api who works (tested with postman) and put it into the console next i'd like to map my users to show it on the app
class Test extends Component {
constructor(props) {
super(props);
this.state = {
users: [],
}
}
componentDidMount() {
console.log("component did mount");
fetch("/user/list")
.then(res => {
return res.json();
})
.then(users =>
this.setState({users}, () =>
console.log("list of users => " + users)));
}
render() {
return (
<div className="form">
<ul>
{this.state.users.map((user) =>
<li key="user._id">{ user.name }</li>
)}
</ul>
</div>
);
}
} export default Test;
Thanks for help !
You are calling res.json() rather than returning res.json() from the first then on your fetch call
I've found this pattern to be helpful:
fetch(url)
.then(res => res.ok ? res.json() : Promise.reject())
As your code is now, users (the parameter in the second then would be undefined, because you are not returning anything from the first then
you have to return the res.json() to use it in the next .then()
.then(res => {
res.json();
})
should be
.then(res =>
res.json();
)
Or
.then(res => {
return res.json();
})
https://javascript.info/promise-chaining
You should be passing your res into res.json() and returning the results into your state.
componentDidMount() {
console.log("component did mount");
fetch("/user/list")
.then(res => res.json())
.then(users =>
this.setState(users,
() => {
console.log("list of users => " + users)
})
);
}
Michael Jasper response help me so much!
I found that fetch with GET method does not work if we pass any request body.
the full example is here
https://github.com/alexunjm/todo-list-react
const buildRequestOptions = ({
method = "GET",
raw = null, // I had my error here!, with GET raw need to be null
customHeaders = {name: 'value'},
}) => {
var myHeaders = buildHeaders(customHeaders);
var requestOptions = {
method,
headers: myHeaders,
body: raw,
redirect: "follow",
};
return requestOptions;
};
const listTasks = () => {
const url = `${uriBase}/task/sample`;
const requestOptions = buildRequestOptions({
customHeaders: { "Content-Type": "application/json" },
});
return fetch(url, requestOptions);
}
const asyncFn = ({
promiseToWait,
pendingFn,
successFn,
errorFn,
}) => {
return (dispatch) => {
dispatch(pendingFn());
promiseToWait
.then((res) => {
if (res.ok) {
return res.json();
}
// handled from server status 422 and 401
if (res.status === 422) {
// error message on body from server
return res.json();
}
if (res.status === 401) {
// custom error message hardcoded
return {errors: {action: 'no authorized'}}
}
console.log("http response no controlled", res);
return Promise.reject();
})
.then((body) => {
if (body.errors) {
const errors = Object.keys(body.errors).map(
(key) => key + " " + body.errors[key]
);
dispatch(errorFn(errors.join("; ")));
} else {
dispatch(successFn(body));
}
return body;
})
.catch((error) => {
console.log("error", error);
dispatch(errorFn("Unavailable server connection"));
});
};
};
const queryTasks = () => {
return asyncFn({
promiseToWait: listTasks(),
pendingFn: apiPending,
successFn: apiSuccessList,
errorFn: apiError,
});
}

React - Fetch multiple apis

I want to make a get request to multiple apis at the same time from 2 different urls, and then I want to just update the array "items" in the state with the new property "img", not to overwrite it. I want to keep and properties in the first request.
Here is my try.
componentDidMount(){
let url = ``;
let url2 = ``
fetch(url,{
method: 'GET'
})
.then((response)=> response.json())
.then((responseJson) => {
const newItems = responseJson.items.map(i => {
return{
itemId: i.itemId,
name: i.name,
};
})
const newState = Object.assign({}, this.state, {
items: newItems
});
console.log(newState);
this.setState(newState);
})
.catch((error) => {
console.log(error)
});
fetch(url2,{
method: 'GET'
})
.then((response)=> response.json())
.then((responseJson) => {
const newImg = responseJson.item.map( data=> {
return{
img: data.picture.url
};
})
const newState = Object.assign({}, this.state, {
items: newImg
});
console.log(newState);
this.setState(newState);
})
.catch((error) => {
console.log(error)
});
}
You absolutely can call two seperate APIs. The problem that you are having is that the API call that is returning last is overwriting the data that was saved from the first API call. Here is the code that will fix this.
componentDidMount(){
let api1 = `https://myapiexample1.com`;
let api2 = `https://myapiexample2.com`;
let promise1 = fetch(api1)
.then(response => response.json())
.then(json => json.items.map(item => {
return {
itemId: item.itemId
name: item.name
}
}))
let promise2 = fetch(api2)
.then(response => response.json())
.then(json => json.items.map(item => {
return {
img: item.img
}
}))
Promise.all([promise1, promise2])
.then(results => results[0].concat(results[1]))
.then(items => this.setState({itmes}))
}
An alternative approach which is not as clean, but is similar to what you are currently doing is to make sure to include the old state when adding new items to the state:
this.setState({
items: newItems.concat(this.state.items)
})
Use Promise.all():
var p1 = Promise.resolve(3);
var p2 = 1337;
var p3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, "foo");
});
Promise.all([p1, p2, p3]).then(values => {
console.log(values); // [3, 1337, "foo"]
});
Ref: https://developer.mozilla.org/it/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
You can use Promise.all, it will resolve when all promises are ok or reject if any fails.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

Resources