How to make a second API call based on the first response? - javascript

I need to call 2 APIs for displaying data on my component but the second api needs headers from the response of first API.
I am using React Hooks. I update the state from the first response so i can use it later for the second call, but it goes undefined. What am i doing wrong?
P.s a better way of doing this calls (maybe using async/await) would be much appriciated
const [casesHeaderFields, setCasesHeaderFields] = useState([]);
const [casesFields, setCasesFields] = useState([]);
useEffect(() => {
const fetchData = () => {
const result1 = axios
.get(`firstUrl`)
.then(response => {
//I need this as headers for my second API
setCasesHeaderFields(
response.data.result.fields
);
});
const result2 = axios
.get(`url${casesHeaderFields} //here i want to pass params from
//first response)
.then(response => {
setCasesFields(response.data.result.record_set);
});
};
fetchData();
}, []);

You can chain the results as they are regular promises:
Ref
axios.get(...)
.then((response) => {
return axios.get(...); // using response.data
})
.then((response) => {
console.log('Response', response);
});

make the second call inside a .then chained to the end of the first promise chain ... in simple terms, chain your promises
Something like
useEffect(() => axios.get(`firstUrl`)
.then(response => {
setCasesHeaderFields(response.data.result.fields);
return response.data.result.fields;
})
.then(casesHeaderFields => axios.get(`url${casesHeaderFields}`))
.then(response => {
setCasesFields(response.data.result.record_set);
}), []);

The queries result1 and result2 look sequential but there are simultaneous. In other words, result2 doesn't wait for result1 to finish before being executed.
Here is a working example to chain promises using async/await:
useEffect(async () => {
try {
// Make a first request
const result1 = await axios.get(`firstUrl`);
setCasesHeaderFields(result1.data.result.fields);
// Use resutlt in the second request
const result2 = await axios.get(`url${"some params from result1"}`);
setCasesFields(result2.data.result.record_set);
} catch (e) {
// Handle error here
}
}, []);
EDIT
Based on comments, here is a best way to use async with useEffect without warning in the console:
useEffect(() => {
const fetchData = async () => {
try {
// Make a first request
const result1 = await axios.get(`firstUrl`);
setCasesHeaderFields(result1.data.result.fields);
// Use resutlt in the second request
const result2 = await axios.get(`url${casesHeaderFields}`);
setCasesFields(result2.data.result.record_set);
} catch (e) {
// Handle error here
}
};
fetchData();
}, []);

You can use async/await to relieve the mess.
useEffect(async () => {
const response = await axios.get(`firstUrl`)
const result = await axios.get(`url${response.data.result.fields}`)
console.log(result.data)
})

You can write something like this:
useEffect(() => {
return axios
.get(`firstUrl`)
.then(response => {
return response.data.result.field
}).then(result => {
return axios.get(`url${result}`)
});
});
With async / await :
useEffect(async () => {
const result1 = await axios
.get(`firstUrl`)
.then(response => {
return response.data.result.field
});
const result2 = await axios.get(`url${result1}`)
return result2
});

Related

How to concat multiple responses and set all response in an array [React JS]

I am doing an API call which is returning IDs and based on number of ids I am doing another call and trying to combine the responses but I am stuck with async issues.
const SearchUser = async () => {
try {
const response = await getSearchUsers();
const ids = response.data?.map((user) => user.userId);
await ids.forEach(async (id) => {
const result = await getUserInfo(id);
setRNOUsers(...result);
// combine result in one state
});
} catch (error) {
setSearching(false);
}
};
useEffect(() => {
SearchUser();
console.log('RNOUsers', RNOUsers); // this is empty and runs even before callng api
}, []);
How can handle this?
You can use Promise.all to wait for all responses, and then set them together with setRNOUsers
const SearchUser = async () => {
try {
const response = await getSearchUsers();
const ids = response.data?.map((user) => user.userId);
const responses = await Promise.all(ids.map(id => getUserInfo(id)))
setRNOUsers(...responses.flatMap(x => x));
} catch (error) {
setSearching(false);
}
};
useEffect(() => {
SearchUser();
console.log('RNOUsers', RNOUsers);
}, []);
Side note, the problem with console.log('RNOUsers', RNOUsers) is setRNOUsers (initialized by useState) is asynchronous. Besides that, your API calls are also asynchronous, so you cannot get values from RNOUsers immediately in useEffect. If you want to see data in that log, you should wait until the state is updated and your component gets re-rendered with your latest data.

Use fetched API data outside of the function

To make my code cleaner I want to use fetched API data in a few different functions, instead of one big. Even though I 've did manage to reffer to that data in other functions, the problem is the API im a fetching throws different, randomized results every time it is called. And so the output from userData() does not equal that from userData2(), even though my intention is different and I'd like the result variable contents to be the same between functions.
const getData = () =>
fetch("https://opentdb.com/api.php?amount=10").then((response) =>
response.json()
);
const useData = async () => {
const result = await getData();
console.log(result);
};
const useData2 = async () => {
const result = await getData();
console.log(result);
};
Your getData() function returns a promise. One fun fact about promises is that while they can only resolve once, that resolved value can be accessed and used as many times as you want.
const dataPromise = getData();
const useData = async () => {
const result = await dataPromise;
console.log(result);
};
const useData2 = async () => {
const result = await dataPromise;
console.log(result);
};
Using await resolves the promise value, the equivalent of...
dataPromise.then((result) => {
console.log(result);
});
// or `dataPromise.then(console.log)` if you like brevity
I like to point this out about the fetch-api... you should always check the Response.ok property
const getData = async () => {
const res = await fetch("https://opentdb.com/api.php?amount=10");
if (!res.ok) {
throw new Error(`${res.status}: ${await res.text()}`);
}
return res.json();
};

Performing an api call on each item in an array and appending the result to a new array

I have an array and I am trying to perform an api call on each item.
Like so -
shoppingData.items.map(item => {
getItemInformation(item.id)
.then(response => response.json())
.then(data => {
JSON.parse(data.text);
});
getItemInformation is my api call -
export async function getItemInformation(id) {
try {
const req = await fetch(`**api url**`);
return await req;
} catch (e) {
console.error(e);
return 'Error';
}
}
However, once I have parsed the data, I would like to append it to a new array. This new array will then be used to render a component later down the page like so -
{newArray?.map((item, index) => (
<ShoppingItem key={index} itemDescription={item.description} itemCatergory={item.catergory} />
))}
Im having issues doing this as I have been trying to do it in a useEffect as ideally I need it to happen when the page renders. Furthermore, I tried having newArray as a state e.g const [newArray, setNewArray] = useState([]) but because I am appending items to an array, setNewArray wouldn't allow me to do this.
use Promise.all
const allItems = await Promise.all(
shoppingData.items.map(
item => getItemInformation(item.id)
.then(response => response.json())
)
);
You could also simplify this a bit by putting all the "asyncy" stuff into your getItemInformation method
const allItems = await Promise.all(
shoppingData.items.map(
item => getItemInformation(item.id)
)
);
and
export async function getItemInformation(id) {
try {
const req = await fetch(`**api url**`);
const json = await req.json();
return json;
} catch (e) {
console.error(e);
return 'Error';
}
}
Live example usiong JSONPlaceholder demo api:
async function getItemInformation(id) {
console.log("getItemInformation",id);
try {
const req = await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`);
const json = await req.json();
return json;
} catch (e) {
console.error(e);
return 'Error';
}
}
(async function testIt() {
const shoppingData = {
items: [{
id: 1
}, {
id: 2
}, {
id: 3
}]
};
const allItems = await Promise.all(
shoppingData.items.map(
item => getItemInformation(item.id)
)
);
console.log(allItems);
})()
You can easily call setNewArray(allItems) in react useEffect using this code (basically where I did consolew.log(allItems) above.
I am not sure how do you append the data to your array while you are calling async function in .map without waiting for the result.
I assume you may be trying something like this:
const parsedData = shoppingData.items.map(item => {
getItemInformation(item.id)
.then(response => response.json())
.then(data => {
JSON.parse(data.text);
});
setNewArray(parsedData);
There are two points to note. Firstly getItemInformation() is called on each item async so the order of parsedItem may not be what your expected. Secondly setNewArray() is called while parsedData's async calls may not finish.
One possible way you can try, if you really want to use .map, is using Promise.all(). Promise.all() resolves when all its promises are resolved:
const getParsedData = async() => {
return Promise.all(shoppingData.items.map(item => getItemInfo.......));
}
const data = await getParsedData();
then append data to your array.
Another simpler solution is using for-loop and await:
for (let i=0;i<shoppingData.items.length;i++){
const parsedItem = await getItemInformation(shoppingData.items[i]).........
newArray.push(parsedItem);
}

Trying to figure out how to go around Promise: Pending situation

I could not find a proper way to get my GET handler working. In the code below, when I send a GET request to that endpoint, my array element are all showing as 'Promise: Pending'. Can you suggest a way to go around it? I tried using setTimeout() method but I feel like it is not a proper solution.
Thank you in advance.
apiRouter.get('/photos', (req,res,next) => {
axios.get("https://graph.instagram.com/me/media?fields=id,caption&access_token={some_access_token}")
.then(response => {
const photosArr = response.data.data;
const arr = photosArr.map(id => {
return axios.get(`https://graph.instagram.com/${id.id}?fields=id,media_type,media_url,username,timestamp&access_token={some_acces_token}`)
.then(response => {
return response.data.media_url;
})
})
res.send(arr);
next();
})
})
This would be a useful case to use async / await
The problem is that you are returning the promise in your Array.map(). Even though you have a .then block after your promise, the promise itself is what is being returned because this is running asynchronously.
Something like this should be close
apiRouter.get('/photos', async (req,res,next) => {
const response = await axios.get("https://graph.instagram.com/me/media?fields=id,caption&access_token={some_access_token}")
const photosArr = response.data.data;
const arr = photosArr.map(async (id) => {
const resp await axios.get(`https://graph.instagram.com/${id.id}?fields=id,media_type,media_url,username,timestamp&access_token={some_acces_token}`)
return resp.data.media_url;
};
const final = await Promise.all(arr);
res.send(final);
next();
})
You can use Promise.all() to wait for all promises in an array to resolve:
apiRouter.get('/photos', (req,res,next) => {
axios.get("https://graph.instagram.com/me/media?fields=id,caption&access_token={some_access_token}")
.then(response => {
const photosArr = response.data.data;
Promise.all(photosArr.map(id => {
return axios.get(`https://graph.instagram.com/${id.id}?fields=id,media_type,media_url,username,timestamp&access_token={some_acces_token}`)
.then(response => {
return response.data.media_url;
})
})).then(photos => {
res.send(photos);
next();
})
})
})```

Why can't I store the result of a promise (or a chained promise) in a variable?

This is a silly question but can you explain what is wrong with this code?
Why can't I perform this?
const fetch = require('node-fetch');
const fetchProm = (() => {
return fetch('https://api.github.com/users/github');
}).then((response) => {
return response.json();
}).then((json) => {
console.log(json)
});
Declaring a function is not the same as calling one
You are not calling the function that returns the promise, just declaring it. You'll need to add an additional set of parentheses before the first .then() in order to actually call the function:
const fetch = require('node-fetch');
const fetchProm = (() => {
return fetch('https://api.github.com/users/github');
})().then((response) => {
return response.json();
}).then((json) => {
console.log(json)
});
If you want to call everything at a later time, you need to place the whole thing in its own function where the promises get handled in an isolated scope:
const fetch = require('node-fetch');
const fetchProm = () => {
fetch('https://api.github.com/users/github')
.then(response => response.json())
.then(json => console.log(json));
};
fetchProm();

Categories

Resources