Multiple API Calls in React - javascript

I am making an app where I receive data from an API. Once I get this data I want to make another call to the same API with the endpoint that I got from the first call.
fetch(req)
.then((response)=>(
response.json()
)).then((json)=>{
console.log(json)
json.meals.map((obj)=>{
let url = `https://spoonacular-recipe-food-nutrition-v1.p.mashape.com/recipes/${obj.id}/information`
let req = new Request(url,{
method: 'GET',
headers: header
})
fetch(req)
.then((response)=>(
response.json()
)).then((json)=>{
console.log(json);
this.setState((prevState)=>{
recipe: prevState.recipe.push(json)
})
})
})
this.setState(()=>{
return{
data: json
}
})
})
I am making two fetch requests here but the problem is the data from the first response is output after second fetch request. Also the state: data gets set before state: recipe and the components render with the data from state: data.
render(){
return(
<div className="my-container">
<EnterCalorie getData={this.getData}/>
<MealData data={this.state.data} recipe={this.state.recipe}/>
</div>
)
}
How can i make sure both get passed down at the same time?

In line 3 return return response.json() instead of nothing (undefined).
Update:
const toJson = response => response.json()
fetch(req)
.then(toJson)
.then(json => {
this.setState(() => {
return {
data: json
}
})
return json
})
.then((json) => {
console.log(json)
const promises = json.meals.map((obj) => {
let url = `https://spoonacular-recipe-food-nutrition-v1.p.mashape.com/recipes/${obj.id}/information`
let req = new Request(url, {
method: 'GET',
headers: header
})
return fetch(req)
.then(toJson)
.then((json) => {
console.log(json);
this.setState((prevState) => ({
recipe: prevState.recipe.push(json)
}))
})
})
return Promise.all(promises)
})
.then(() => {
console.log('job done')
})
You need to map your array into promises. Then use Promise.all to wait for them the get resolved.
There was parenthesis missing from:
this.setState((prevState)=>{
recipe: prevState.recipe.push(json)
})
A sidenote, this whole stuff should be refactored. You're not going to get far with this code style / code complexity.

fetch(req) // req no1
.then((response)=>(
response.json()
)).then((json)=>{
console.log(json)
json.meals.map((obj)=>{
let url = `https://spoonacular-recipe-food-nutrition-v1.p.mashape.com/recipes/${obj.id}/information`
let req = new Request(url,{
method: 'GET',
headers: header
})
fetch(req) // req no 1 called again
.then((response)=>(
response.json()
)).then((json1)=>{
console.log(json1);
this.setState((prevState)=>{
recipe: prevState.recipe.push(json1)
})
this.setState(()=>{
return{
data: json
})
})
})
})
})
I think you are calling api with same req parameters again in the second fetch call

This is a callback hell, please look for Promise races, and check the all() promise method.

Related

Getting .json is not a function on Promise.all w/fetch

Oh once again I have those Promise.all blues:( I have a function that makes an array of fetch call's from provided urls and then we want to retrieve data via a Promise.all and return array of reponses or better yet just return the promise to calling function. . The problem is this results in error w/console showing:
There was problem retrieving data. TypeError: r.json is not a function
The code for the function is :
const getLeagueLeaders = (url, params) => {
// First let's create the array of url's
let queryURLs = [];
params.forEach((param) => {
queryURLs.push(
fetch(`${url}${new URLSearchParams(param)}`, {
method: "get",
headers: {
Authorization:
"Basic ==",
},
}).then((res) => res.json())
);
});
return (
Promise.all(queryURLs)
// map array of responses into an array of response.json() to read their content
.then((responses) => responses.map((r) => r.json()))
.catch((err) => {
console.error("There was problem retrieving data.", err);
})
);
};
module.exports = getLeagueLeaders;
And in Vue component
mounted: async function () {
const leagueLeadersResponseArray = await getLeagueLeaders(
this.fetchBaseUrl,
this.params
);
this.qbLeaders =
leagueLeadersResponseArray[0].cumulativeplayerstats.playerstatsentry;
Obviously leagueLeadersResponseArray is undefined. I researched .json() and dont see how I am using it incorrectly. At first i thought I needed a Promise.all wrapper for the responses.map((r) => r.json()) but that did no good either. I looked at this link but I am not using fetch as he is. Any guidance much appreciated....
Updated working code for anybody else:
// ---------- src/js/modules/ ------------------ //
/* jshint ignore:start */
// Make function to retrieve League Leaders in a Category
const getLeagueLeaders = (url, params) => {
// First let's create the array of url's
let queryURLs = [];
params.forEach((param) => {
queryURLs.push(
fetch(`${url}${new URLSearchParams(param)}`, {
method: "get",
headers: {
Authorization:
"Basic ==",
},
}).then((res) => res.json())
);
});
return Promise.all(queryURLs).catch((err) => {
console.error("There was problem retrieving data.", err);
});
};
module.exports = getLeagueLeaders;
Your template string is around the entire fetch when it should only be in the argument to fetch:
params.forEach((param) => {
queryURLs.push(fetch(`${url}${new URLSearchParams(param)}`, {
method: "get",
headers: {
Authorization:
"Basic *****==",
}
}));
});
Then, you have a .then(data => {return data}), which doesn't do anything since the return returns from the then callback, not the function. You should instead return the promise that Promise.all gives you:
return Promise.all(queryURLs)
// map array of responses into an array of response.json() to read their content
.then((responses) => responses.map((r) => r.json())) // Get error There was problem retrieving data. TypeError: r.json is not a function
.catch((err) => {
console.error("There was problem retrieving data.", err);
});

Fetch Result Return with status code and json data

I have a fetch API call that calls API back end and in return, I will get the response object with status code. What I am trying to do is base on return, I wanted to return the JSON response with status code. so that other part of the javascript can manipulate base on status code. My fetch function is as follow.
I have tried with as follow below, but it returns as a given screenshot. It gives me promise value which I didn't want to get.
export const createUser = ( posts ) => {
const apiRoute= "/api/register";
return window.fetch(`${apiRoute}`, {
"headers": headers,
method: "POST",
body: JSON.stringify(posts)
}).then(response => ({
'status' : response.status,
'data' : response.json()
}))
.catch(error => console.error('Error: ', error))
;
}
I know that it might be the duplicate from this post (Fetch api - getting json body in both then and catch blocks for separate status codes), but I do not want my data to return as a promise. Instead, I wanted to return fully constructed well form JSON data.
Something like this.
{status: 400, data: {id:1,name:Hello World}}
how can i achieve this?
"It gives me promise value"
That's right, as per the documentation.
You need to resolve that promise before resolving the outer promise.
For example
.then(response => {
return response.json().then(data => ({
status: response.status,
data
}))
})
Alternatively, use an async function
export const createUser = async ( posts ) => {
const apiRoute= "/api/register";
try {
const response = await window.fetch(apiRoute, {
headers,
method: "POST",
body: JSON.stringify(posts)
})
return {
status: response.status,
data: await response.json()
}
} catch (error) {
console.error('Error: ', error)
throw error
}
}

Get response headers object in redux with React.js

Using redux in React.js I get the most starred repositories in the last 30 days, now I wanna use the pagination that github api provides but to do so I have to use the headers in the response, how can I do that, how can I change my code to get the headers from the response, this is the function that gets the response:
import getDate from './getDate';
export function fetchRepos() {
return function(dispatch) {
dispatch({
type: "FETCH_REPOS_REQUEST",
});
return fetch(
"https://api.github.com/search/repositories?q=created:>" +
getDate() +
"&sort=stars&order=desc",
)
.then(response => response.json().then(body => ({response, body})))
.then(({response, body}) => {
if (!response.ok) {
dispatch({
type: "FETCH_REPOS_FAILURE",
error: body.error,
});
} else {
dispatch({
type: "FETCH_REPOS_SUCCESS",
repos: body.items,
});
}
});
};
}
Please help, thank you!
I like to assemble a response object that includes the headers as an object for fetch calls like so:
fetch('https://jsonplaceholder.typicode.com/users')
.then(res => (res.headers.get('content-type').includes('json') ? res.json() : res.text())
.then(data => ({
headers: [...res.headers].reduce((acc, header) => {
return {...acc, [header[0]]: header[1]};
}, {}),
status: res.status,
data: data,
}))
.then(response => console.log(response)));
in your case you could then simply get the headers with response.headers in the last .then().
but technically you can access the headers with res.headers.get('<header.name>').

fetch inside fetch error setState

I have a problem solving this react.js
loadFromServer(pageSize) {
fetch('http://localhost:8080/api/employees')
.then(response => {
return fetch('http://localhost:8080/api/profile/employees',
{
headers: new Headers({
'Accept': 'application/schema+json'
})
}).then(schema => {
this.scheme = schema;
return response.json();
}
)
})
.then(response =>
this.setState(
{
employees: response._embedded.employees,
attributes: Object.keys(this.scheme.json().properties),
pageSize: pageSize,
links: response._links}
)
);
}
at this part
attributes: Object.keys(this.scheme.json().properties),
always return (in promise) TypeError: Cannot convert undefined or null to object.
If I put console.log(this.scheme.json()) I can see the Promise but, why inside setState I get null object?
There are a few issues here:
The main one is that this.schema.json() returns a promise (as you know from your console.log). Promises don't have a properties property, so you're passing undefined to Object.keys, which then gives you that error.
You're also not checking for errors from fetch, in two different ways: You're not checking .ok (which is such a common error I've posted about it on my anemic little blog), and you're not checking for promise rejection.
You're also doing some unnecessary promise nesting and could be overlapping your fetch calls more.
First, since it seems you often fetch JSON, I'd suggest giving yourself a utility function for it:
function fetchJSON(...args) {
return fetch(...args)
.then(response => {
if (!response.ok) {
throw new Error('HTTP error ' + response.status);
}
return response.json();
});
}
Notice the .ok check.
Then, also in the "break the problem into smaller pieces" category, I'd have a fetchSchema function:
function fetchSchema(url) {
return fetchJSON(url, {
headers: new Headers({
'Accept': 'application/schema+json'
})
});
}
Then, loadFromServer can use Promise.all and destructuring to run the operations in parallel:
// (I assume this is in a `class` or object initializer, as it doesn't have `function` in front of it)
loadFromServer(pageSize) {
Promise.all(
fetchJSON('http://localhost:8080/api/employees'),
fetchSchema('http://localhost:8080/api/profile/employees')
)
.then(([empResponse, schema]) => {
this.schema = schema;
this.setState({
employees: empResponse._embedded.employees,
attributes: Object.keys(schema.properties),
pageSize: pageSize,
links: empResponse._links
})
)
.catch(error => {
// Do something with the error
});
}
Note the .catch, since you're not returning the promise from loadFromServer. (If you want to buck errors up the chain, add return in front of Promise.all and move the .catch to calling code.)
Side note: Your code used
this.scheme = schema;
Note that the property on the left is scheme (with a final e) but the variable is schema (with a final a). I think you meant schema and so I've included that change in the above, but if the property is really supposed to be this.scheme, you'll want to adjust that. Or if you don't need that property for anything other than the code in loadFromServer, remove that line entirely.
I think you should use Promise.all to run the two requests in parrallel and then retrieve the two responses (by the way response.json() returns a Promise, that's why you have an error in your code) :
loadFromServer(pageSize) {
Promise.all([
fetch('http://localhost:8080/api/employees')
.then(response => {
if (!response.ok) throw Error(response.statusText);
return response.json();
),
fetch('http://localhost:8080/api/profile/employees')
.then(response => {
if (!response.ok) throw Error(response.statusText);
return response.json();
),
]).then(responses => {
this.setState({
employees: responses[0]._embedded.employees,
attributes: Object.keys(responses[1].properties),
pageSize: pageSize,
links: responses[0]._links
})
}).catch(error => {...})
}
I think you need something like:
loadFromServer(pageSize) {
fetch('http://localhost:8080/api/employees')
.then(response => {
return fetch('http://localhost:8080/api/profile/employees', {
headers: new Headers({
'Accept': 'application/schema+json'
})
}).then(schema => {
schema.json().then(data => {
this.scheme = data
})
});
return response.json();
})
.then(response =>
this.setState({
employees: response._embedded.employees,
attributes: Object.keys(this.scheme.properties),
pageSize: pageSize,
links: response._links
})
);
}
Response json() method in Fetch API returns a promise. For this reason fetch requests should be consistently chained with .then(response => response.json()) to get a plain object.
Flattening promises may result in more reliable control flow. Since responses from both requests are used, this would require to either nest then callbacks or passing another response through then chain. async may be useful because it conveniently solves flattening problem:
async loadFromServer(pageSize) {
const employeesResponse = await fetch('http://localhost:8080/api/employees', {
headers: new Headers({ 'Accept': 'application/schema+json' })
});
const employees = await employeesResponse.json();
const schemeResponse = await fetch('http://localhost:8080/api/profile/employees', {
headers: new Headers({ 'Accept': 'application/schema+json' })
});
const scheme = await schemeResponse.json();
this.setState({
employees: employees._embedded.employees,
attributes: Object.keys(scheme.properties),
pageSize: pageSize,
links: response._links
});
}
Since requests don't depend on each other, they could be performed in parallel with Promise.all.
async loadFromServer(pageSize) {
const employeesPromise = fetch('http://localhost:8080/api/employees', {
headers: new Headers({ 'Accept': 'application/schema+json' })
})
.then(res => res.json());
const schemePromise = fetch('http://localhost:8080/api/profile/employees', {
headers: new Headers({ 'Accept': 'application/schema+json' })
})
.then(res => res.json());
const [employees, scheme] = await Promise.all([employeesPromise, schemePromise]);
this.setState({
employees: employees._embedded.employees,
attributes: Object.keys(scheme.properties),
pageSize: pageSize,
links: response._links
});
}

async await into actionCreator, should i return the result?

Into my actionCreators I call some rest API endpoints on action e.g. UPDATE_CART_ITEM etc.
At first I was using axios like so return axios()...:
export const login = (username, password) => (dispatch) => {
dispatch(requestLogin())
const URL = `${USERS_URL}?username=${username}&password=${password}`
return axios(URL)
.then((response) => {
return response.data
})
.then((user) => {
dispatch(loginSuccess(user))
// save the user on localStorage
localStorage.setItem('user', JSON.stringify(user))
// direct the logedin user to the games page
history.push('/')
})
.catch(() => {
return dispatch(loginFailure())
})
}
Now I use async/await like so:
// On payload i have the obj: {prId: 'xxxxx'}
export const updateCartItem = (payload) => async (dispatch) => {
const response = await fetch('cart/update',
{
body: JSON.stringif(payload),
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
method: 'POST',
})
// I m not sure if i have to return the result
const result = await response.json()
// I dispatch the action with the payload
await dispatch(return {
payload,
type: UPDATE_CART_ITEM,
})
} catch (err) {
dispatch(cartActionFail(err))
}
}
So, inside the updateCartItem function, how should I handle the result ?
Since I'm passing payload to the reducer, it seems that I don't need it.
You probably want to do something like this:
dispatch({ payload: response, type: UPDATE_CART_ITEM })
dispatch(return { /*...*/ }) doesn't make sense as far as I know, and dispatch() doesn't return a promise so there's no point in awaiting on it.
In general, if you want to replace promise chains with async/await, then you want to replace foo.then(bar => { baz(bar); }) with const bar = await foo; baz(bar);
If you need to consume your result immediatly, then you should dispatch an action like UPDATE_CART_ITEM_SUCCEED, otherwise do nothing.
BTW, I recommend you to use redux-saga or redux-thunk to handle your app side effects such as API calls.
If you use the same payload for your action creator what happens if something goes wrong at your backend? Your backend side won't change but your state won't be aware of that and updates itself with payload. This is why you should use some error check here. Also, personally I use last result as a payload to my action creators, not the original payload.
export const updateCartItem = payload => async ( dispatch ) => {
try {
const response = await fetch(
"cart/update",
{
body: JSON.stringif( payload ),
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
method: "POST",
}
);
if ( !response.ok ) { throw new Error( "response error" ); }
const result = await response.json();
return dispatch( {
payload: result,
type: UPDATE_CART_ITEM,
} );
} catch ( error ) {
return dispatch( cartActionFail( error.message ) );
}
};
You can change and enhance this logic according to your needs. As #vkarpov15 pointed out dispatch does not use return explicitly and it does not return a promise, hence you don't need await there.

Categories