I have multiple api's that are returning same type of data but from different areas but the data type is the same only the values are different, and I want to store them all in one react state.
So I have this state:
let [infoData1, setInfoData1] = useState({ infoData1: [] });
let [infoData2, setInfoData2] = useState({ infoData2: [] });
and the axios calls :
function multipleApiCall() {
const headers = {
"X-Api-Key": "the-api-key-00",
};
axios.get(
"http:url-to-data/ID1",
{ headers }
)
.then((response) => {
setInfoData1(response.data);
return axios.get(
"http:url-to-data/ID2",
{ headers }
)
})
.then(response => {
setInfoData2(response.data);
})
}
and afterward I want to use a .map() to list the result but because I have 2 states I cannot concatenate them. So how can I have all data from those two api's in just one state or maybe another approach ?
const [infoData, setInfoData] = useState([]);
const headers = {
"X-Api-Key": "the-api-key-00",
};
const urls = ["http:url-to-data/ID1", "http:url-to-data/ID2"];
function multipleApiCall() {
const promises = urls.map(url => axios.get(url, { headers }));
Promise.all(promises).then(responses => {
let data = [];
responses.forEach(response => {
data = data.concat(response.data);
});
setInfoData(data);
});
}
Related
I'm using Firebase Realtime Database and Firebase Storage in this application, the goal is to upload in Firebase Storage the images in the pictures array, and then get the link of the Firebase Storage to that image and add it in the object which will be pushed in imagesUriArray and added to Realtime Database. The problem is that when I press addItem it successfully update the id, but the images parameter remains empty. And in fact, imagesUriArray remains empty unless I refresh the screen.
export default function NewItem({ route, navigation }) {
const [pictures, setPictures] = useState([]);
const [imagesUriArray, setImageUriArray] = useState([]);
useEffect(() => {
navigation.setOptions({
headerRight: () => (
<TouchableOpacity onPress={addItem}>
<Text style={{fontWeight:'bold'}}>ADD ITEM</Text>
</TouchableOpacity>
)
})
})
const addItem = async () => {
uploadImages()
const changes = ref(db, path)
get(changes).then(async (snapshot) => {
if (snapshot.val().data !== undefined) {
const fetchedArray = snapshot.val().data
let array = fetchedArray;
let object = {
"id": `${Math.random()}`,
"images": imagesUriArray,
}
array.push(object)
update(changes, {
data: array
})
}
})
}
}
const uploadImages = () => {
const metadata = {
contentType: 'image/jpeg',
};
pictures.forEach( async (obj) => {
const id = obj.id
const uri = obj.uri
const response = await fetch(uri);
const blob = await response.blob();
var ref = storageUpload(storage, path)
await uploadBytes(ref, blob, metadata)
await getDownloadURL(ref)
.then((metadata) => {
let array = imagesUriArray
let object = {
"id": id,
"uri": metadata
}
array.push(object)
setImageUriArray(array)
})
.catch((error) => {
console.log(error)
});
})
}
return(
..............
)
}
Issue
This appears to be a state mutation in the uploadImages callback. A reference to the imagesUriArray state is cached locally, mutated (i.e. direct push into array), and then the same reference is saved back into state. This doesn't trigger react to rerender with any updated state value.
const uploadImages = () => {
...
pictures.forEach( async (obj) => {
const id = obj.id
const uri = obj.uri
const response = await fetch(uri);
const blob = await response.blob();
var ref = storageUpload(storage, path)
await uploadBytes(ref, blob, metadata)
await getDownloadURL(ref)
.then((metadata) => {
let array = imagesUriArray // <-- reference to state
let object = {
"id": id,
"uri": metadata
}
array.push(object) // <-- state mutation
setImageUriArray(array) // <-- same state reference
})
.catch((error) => {
console.log(error)
});
})
};
Solution
Use a functional state update to update from the previous state. Create a shallow copy of the previous state and append the new data.
const uploadImages = () => {
...
pictures.forEach( async (obj) => {
const { id, uri } = obj;
const response = await fetch(uri);
const blob = await response.blob();
const ref = storageUpload(storage, path);
await uploadBytes(ref, blob, metadata);
await getDownloadURL(ref)
.then((uri) => {
setImageUriArray(imagesUriArray => [
... imagesUriArray, // <-- shallow copy
{ id, uri }, // <-- append new object
]);
})
.catch(console.log);
})
};
Update
uploadImages needs to return a Promise so that addItem can wait for it to complete its asynchronous code. addItem also needs to access the updated imagesUriArray that uploadImages updates.
Map the pictures array to an array of Promises (i.e. an async fetchImageUri function) that eventually returns the object with id and new uri properties.
const uploadImages = () => {
...
const fetchImageUri = async ({ id, uri }) => {
try {
const response = await fetch(uri);
const blob = await response.blob();
const ref = storageUpload(storage, path);
await uploadBytes(ref, blob, metadata);
const newUri = await getDownloadURL(ref);
return { id, uri: newUrl };
} catch(error) {
console.log(error);
}
}
return Promise.all(pictures.map(fetchImageUri));
};
Update addItem to wait for the resolved array of Promises that contain the uploaded image data. Enqueue the imagesUriArray state update here, then continue the rest of the function referencing the returned uploadedImages array from uploadImages.
const addItem = async () => {
const uploadedImages = await uploadImages();
setImageUriArray(imagesUriArray => [
...imagesUriArray, // <-- shallow copy
...uploadedImages, // <-- append new objects
]);
const changes = ref(db, path);
get(changes).then(async (snapshot) => {
if (snapshot.val().data !== undefined) {
const fetchedArray = snapshot.val().data;
const object = {
id: `${Math.random()}`,
images: uploadedImages,
};
update(changes, { data: [...fetchedArray, object] });
}
});
}
I am attempting to build a bot that will periodically poll an API using axios for the price of multiple cryptocurrencies across multiple exchanges. I need to be able to then look at this stored data to analyse price differences of a given token between multiple exchanges to calculate if there is profit to be made if I were to purchase it on one and then sell on another.
So far I am able to get access to the price data and console.log it as the request is returned, however I need to add this data to an array so that it can be analysed at a later point. I understand that I will need to use promises to handle this but I find it slightly confusing.
The following code immediately outputs an empty array and then outputs all the individual prices. How can I gain access to those values at a later point in the code?
const axios = require('axios');
const { exchanges } = require('./resources/exchanges.json');
const { currencies } = require('./resources/currencies.json');
const buyCurrency = currencies.buy[0];
const poll = async () => {
const data = new Array();
await currencies.sell.forEach(async sellCurrency => {
await exchanges.forEach(async exchange => {
try {
const allOtherExchanges = exchanges.filter(x => x !== exchange).map(x => `,${x}`).join();
const response = await axios.get(`https://api.0x.org/swap/v1/quote?buyToken=${buyCurrency}&sellToken=${sellCurrency}&sellAmount=1000000000000000000&excludedSources=0x${allOtherExchanges}`)
if (response && response.data.price) {
console.log(exchange, sellCurrency, response.data.price)
data.push({
exchange,
price: response.data.price
});
}
} catch {}
});
});
console.log(data);
};
poll()
One of the solutions would be as follows:
const axios = require('axios');
const { exchanges } = require('./resources/exchanges.json');
const { currencies } = require('./resources/currencies.json');
const buyCurrency = currencies.buy[0];
const poll = async () => {
const data = new Array();
const promises = [];
currencies.sell.forEach(sellCurrency => {
exchanges.forEach(exchange => {
const allOtherExchanges = exchanges.filter(x => x !== exchange).map(x => `,${x}`).join();
promises.push(
axios.get(`https://api.0x.org/swap/v1/quote?buyToken=${buyCurrency}&sellToken=${sellCurrency}&sellAmount=1000000000000000000&excludedSources=0x${allOtherExchanges}`)
.then(response => {
if (response && response.data &&response.data.price) {
console.log(exchange, sellCurrency, response.data.price)
data.push({
exchange,
price: response.data.price
});
}
}).catch(err => console.error(err))
);
});
});
await Promise.all(promises);
console.log(data);
};
poll();
For the API I'm using, I have to bounce off 3 separate endpoints to get the data I need. I'm stuck on the very last endpoint and it's driving me crazy. Here's the gist of what I'm currently doing (or attempting to do).
Make direct call to ENDPOINT 1. Process data through map (to return data I need specifically) then push to ARRAY 1.
Once ARRAY 1 is done processing, I map ARRAY 1's data to make an API call for each of it's IDs to ENDPOINT 2, then push this to ARRAY 2.
Once ARRAY 2 is done processing, I map ARRAY 2's data to make an API call for each of it's IDs to ENDPOINT 3, then push this to ARRAY 3.
All of these steps are wrapped in a promise that resolves with the 3 completed arrays.
Steps 1 and 2 get done just fine, but I have tried a million different things for step 3 and it keeps returning . What would be the best way for this to be handled? Any help would be MUCH appreciated!
router.get("/", (req, res) => {
let artists = [];
let albums = [];
let tracks = [];
const options = {
headers: {
'Authorization': `Bearer ${token}`,
},
};
function getArtists(url) {
return new Promise(resolve => {
axios.get(url, options).then(response => {
artists.push(...response.data.artists.items.map(artist => ({
url: artist.external_urls.spotify,
name: artist.name,
images: artist.images,
id: artist.id,
genres: artist.genres,
})));
let next = response.data.artists.next;
if (next !== null) {
getArtists(next);
} else {
resolve(getAlbums().then(() => getTracks().then(() => res.send({artists, albums, tracks}))));
};
});
});
};
let getAlbums = () => {
return new Promise(resolve => {
const requests = artists.map(item => {
return axios.get(`https://api.spotify.com/v1/artists/${item.id}/albums?market=us&include_groups=single,appears_on`, options).then(response => {
albums.push(...response.data.items);
});
});
Promise.all(requests).then(() => {
const filtered = albums.filter((curr, index, self) => self.findIndex(t => t.id === curr.id) === index);
const sorted = filtered.sort((a, b) => (b.release_date > a.release_date) ? 1 : -1); // change data to filtered to filter duplicates
const sliced = sorted.slice(0, 50);
albums = sliced;
// res.send({artists, albums});
resolve();
});
});
};
let getTracks = () => {
albums.map(item => {
return axios.get(`https://api.spotify.com/v1/albums/${item.id}/tracks`, options).then(response => {
tracks.push(...response.data.items);
});
});
};
if (token) {
const url = 'https://api.spotify.com/v1/me/following?type=artist&limit=50';
getArtists(url);
} else {
res.send({
message: 'Please login to retrieve data',
});
};
});
I think you might be better to follow a simpler approach, using async/await. This allows us to structure the code in an easier to follow way. If we have loads of .then() and new Promises etc, it gets very confusing, very quickly!
I've restructured like so, I think this is easier to follow and hopefully to debug!
We can loop over each item in the getXXX functions, or we can use Promise.all, either approach works, though the latter may be more performant.
I've used this in getTracks()
router.get("/", async (req, res) => {
let options = {
headers: {
'Authorization': `Bearer ${token}`,
}
}
if (!token) {
res.send({
message: 'Please login to retrieve data',
});
return;
}
const url = 'https://api.spotify.com/v1/me/following?type=artist&limit=50';
let artists = await getArtists(url, options);
console.log ("Artists (length):", artists.length );
let albums = await getAlbums(artists, options);
console.log ("Albums (length):", albums.length );
let tracks = await getTracks(albums, options);
console.log("Tracks (length):", tracks.length);
res.send( { albums, artists, tracks } );
}
async function getArtists(url, options, maxLoopCount = 100) {
let artists = [];
let count = 0;
do {
console.log(`getArtists: Page #${++count}...`);
let artistResp = await getArtistsPage(url, options);
artists.push(...artistResp.artistList);
url = artistResp.next;
} while (url && count < maxLoopCount) ;
return artists;
}
async function getArtistsPage(url, options) {
let response = await axios.get(url, options);
let artistList = response.data.artists.items.map(artist => ({
url: artist.external_urls.spotify,
name: artist.name,
images: artist.images,
id: artist.id,
genres: artist.genres,
}));
let next = response.data.artists.next;
return { artistList, next}
};
async function getAlbums(artists, options, sliceCount = 50) {
let albums = [];
for(let artist of artists) {
let response = await axios.get(`https://api.spotify.com/v1/artists/${artist.id}/albums?market=us&include_groups=single,appears_on`, options);
albums.push(...response.data.items);
}
const filtered = albums.filter((curr, index, self) => self.findIndex(t => t.id === curr.id) === index);
const sorted = filtered.sort((a, b) => (b.release_date > a.release_date) ? 1 : -1); // change data to filtered to filter duplicates
const sliced = sorted.slice(0, sliceCount);
return sliced;
}
async function getTracks(albums, options) {
let promises = albums.map(album => axios.get(`https://api.spotify.com/v1/albums/${album.id}/tracks`, options));
let responseList = await Promise.all(promises);
return responseList.map(response => response.data.items).flat();
}
I'm using the following function to get data (results) from the pokemon api:
const [data, setData] = useState({ results: []})
useEffect(() => {
const fetchData = async () => {
const response = await api.get('/pokemon');
setData(response.data);
};
fetchData();
}, []);
My API function:
const api = axios.create({
baseURL: 'https://pokeapi.co/api/v2'
});
And this is the response I have
{
"count": 964,
"next": "https://pokeapi.co/api/v2/pokemon?offset=20&limit=20",
"previous": null,
//data that I need to use
"results": [
{
"name": "bulbasaur",
"url": "https://pokeapi.co/api/v2/pokemon/1/"
//the get request in the URL above can work both with name or number in pokedex.
},
//the list goes on till the 20th pokemon.
]
How can I perform a get request from the url that's inside of the object in results array?
If you want the response and the details for each individual pokemon from you API you map over the response.results and use Promise.all to make a series of API calls and resolve it.
useEffect(() => {
const fetchData = async () => {
const response = await api.get('/pokemon');
// if you want to get the details as well
// details.data will be an array with details for all the pokemon that you get
// from response
const details = await Promise.all(response.data.results.map((el) => {
return axios.get(`/pokemon/${el.name}`)
}));
};
fetchData();
}, []);
const fetchData = async () => {
const response = await api.get('/pokemon');
setData(response.data);
const { url } = response.data.results[0];
const reponse2 = axios.get(url);
// do what you want with response2
};
alternatively, you may want to loop through the results
for(const result of response.data.results){
const { url } = result;
const reponse = axios.get(url);
// do something with response
}
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.