React.js fetch multiple endpoints of API - javascript

I am doing a React.js project. I am trying to pull data from an API that has multiple endpoints. I am having issues with creating a function that pulls all the data at once without having to do every endpoint separetly. The console.log gives an empty array and nothing gets display. The props 'films' is data from the parent and works fine. It is also from another enpoint of the same API. This is the code:
import { useEffect, useState } from "react";
import styles from './MovieDetail.module.css';
const MovieDetail = ({films}) => {
const [results, setResults] = useState([]);
const fetchApis = async () => {
const peopleApiCall = await fetch('https://www.swapi.tech/api/people/');
const planetsApiCall = await fetch('https://www.swapi.tech/api/planets/');
const starshipsApiCall = await fetch('https://www.swapi.tech/api/starships/');
const vehicleApiCall = await fetch('https://www.swapi.tech/api/vehicles/');
const speciesApiCall = await fetch('https://www.swapi.tech/api/species/');
const json = await [peopleApiCall, planetsApiCall, starshipsApiCall, vehicleApiCall, speciesApiCall].json();
setResults(json.results)
}
useEffect(() => {
fetchApis();
}, [])
console.log('results of fetchApis', results)
return (
<div className={styles.card}>
<div className={styles.container}>
<h1>{films.properties.title}</h1>
<h2>{results.people.name}</h2>
<p>{results.planets.name}</p>
</div>
</div>
);
}
export default MovieDetail;
UPDATE
I just added the post of Phil to the code and I uploaded to a codesanbox

You want to fetch and then retrieve the JSON stream from each request.
Something like this
const urls = {
people: "https://www.swapi.tech/api/people/",
planets: "https://www.swapi.tech/api/planets/",
starships: "https://www.swapi.tech/api/starships/",
vehicles: "https://www.swapi.tech/api/vehicles/",
species: "https://www.swapi.tech/api/species/"
}
// ...
const [results, setResults] = useState({});
const fetchApis = async () => {
try {
const responses = await Promise.all(Object.entries(urls).map(async ([ key, url ]) => {
const res = await fetch(url)
return [ key, (await res.json()).results ]
}))
return Object.fromEntries(responses)
} catch (err) {
console.error(err)
}
}
useEffect(() => {
fetchApis().then(setResults)
}, [])
Each URL will resolve to an array like...
[ "people", [{ uid: ... }] ]
Once all these resolve, they will become an object (via Object.fromEntries()) like
{
people: [{uid: ... }],
planets: [ ... ],
// ...
}
Take note that each property is an array so you'd need something like
<h2>{results.people[0].name}</h2>
or a loop.

Related

how to efficiently retrieve data from firebase/Firestore subcollection?

I'm using firestore to store posts each post could have simple properties such as {title: 'hi', comment: true} I'm able to easily fetch the user's specific posts since my collection structure looks like this: posts/user.id/post/post.name so an example will be posts/1234sofa/post/cool day
with this way of structuring, I'm able to easily fetch data for the user, but I'm having trouble with two things how do I fetch and display all posts for my main feed, and what's the most effective way of doing this? here is my current function for fetching user-specific data:
const submitpost = async () => {
try {
const collectionRef=collection(db,`posts`,user.uid.toString(),'post')
await addDoc(collectionRef, {
post: post,
timestamp: serverTimestamp(),
canComment: switchValue,
user: user.uid,
avatar: user.photoURL,
username: user.displayName,
});
toast({ title: "posted", status: "success", duration: 2000 });
} catch (error) {
console.log(error);
}
};
this specific function creates a structure like this in firebase posts are just takes and take is singular post respectively I just changed the name so its easier to understand:
now here is how im fetching the data for my spefic user:
const [user] = useAuthState(auth);
const [takes, settakes] = useState([]);
const getData = async () => {
// if user is present run function
if (user) {
// const docRef = doc(db, "users", user.uid);
// const collectionRef = collection(docRef, "takes");
// const querySnapshot = await getDocs(collectionRef);
try {
const docRef = doc(db, "posts", user.uid);
const collectionRef = collection(db,'posts',user.uid,'takes');
const querySnapshot = await getDocs(collectionRef);
const data = querySnapshot.docs.map((d) => ({
id: d.id,
...d.data(),
}));
settakes(data);
} catch (error) {
console.log(error);
}
//
}
};
here is the function that doesn't work when fetching all data for main feed:
const [user]=useAuthState(auth)
const [allfeed, setallfeed] = useState([])
const getData = async () => {
if(user){
const collectionRef = collection(db, "posts");
const querySnapshot = await getDocs(collectionRef);
const data = querySnapshot.docs.map((d) => ({
id: d.id,
...d.data(),
}));
// get data from firebase
setallfeed(data)
}
}
useEffect(() => {
getData()
console.log('ran');
console.log(allfeed);
// rerun when user is present
}, [user]);
when I console log the allfeed it returns an empty array so my main problem is how to do I get all the data from the posts collection meaning posts/userid/post/post.title I need to get these for every user. and secondly is there a more efficient way to structure my data?
I would suggest using the onSnapshot() method if you want realtime updates from a collection or a specific document.
setState() does not make changes directly to the state object. It just creates queues for React core to update the state object of a React component. If you add the state to the useEffect, it compares the two objects, and since they have a different reference, it once again fetches the items and sets the new items object to the state. The state updates then triggers a re-render in the component. And on, and on, and on...
If you just want to log your data into your console then you must use a temporary variable rather than using setState:
const getData = async () => {
if(user){
// Using `getDocs`
const collectionRef = collection(db, "posts");
const querySnapshot = await getDocs(collectionRef);
const data = querySnapshot.docs.map((d) => ({
id: d.id,
...d.data(),
}));
console.log(data)
// ============================================= //
// Using `onSnapshot()`
const q = query(collection(db, "posts"));
const unsubscribe = onSnapshot(q, (querySnapshot) => {
const data = querySnapshot.docs.map(d => ({
id: d.id,
...d.data()
}))
console.log(data)
});
}
}
useEffect(() => {
getData();
}, []);
You could also use multiple useEffect() to get the updated state of the object:
const getData = async () => {
if(user){
// Using `getDocs`
const collectionRef = collection(db, "posts");
const querySnapshot = await getDocs(collectionRef);
const data = querySnapshot.docs.map((d) => ({
id: d.id,
...d.data(),
}));
setallfeed(data)
// ============================================= //
// Using `onSnapshot()`
const q = query(collection(db, "posts"));
const unsubscribe = onSnapshot(q, (querySnapshot) => {
const data = querySnapshot.docs.map(d => ({
id: d.id,
...d.data()
}))
setallfeed(data)
});
}
}
useEffect(() => {
getData();
}, [])
useEffect(() => {
console.log(allfeed);
}, [allfeed]);
If you want to render it to the component then you should call the state in the component and map the data into it. Take a look at the sample code below:
const getData = async () => {
if(user){
// Using `getDocs`
const collectionRef = collection(db, "posts");
const querySnapshot = await getDocs(collectionRef);
const data = querySnapshot.docs.map((d) => ({
id: d.id,
...d.data(),
}));
setallfeed(data)
// ============================================= //
// Using `onSnapshot()`
const q = query(collection(db, "posts"));
const unsubscribe = onSnapshot(q, (querySnapshot) => {
const data = querySnapshot.docs.map(d => ({
id: d.id,
...d.data()
}))
setallfeed(data)
});
}
}
useEffect(() => {
getData()
}, []);
return (
<div>
<p>SomeData: <p/>
{items.map((item) => (
<p key={item.id}>{item.fieldname}</p>
))}
</div>
);
For more information you may checkout these documentations:
Get data with Cloud Firestore
Get realtime updates with Cloud Firestore

React Native how to get only some data from api

Given url Data==> need to get only Karnataka state details
[{"id":1,"title":"TELANGANA","image":"url","stateCode":"TS"},{"id":4,"title":"TAMILNADU","image":"url","stateCode":"TN"},{"id":3,"title":"KARNATAKA","image":"url","stateCode":"KN"},{"id":2,"title":"ANDHRA","image":"url","stateCode":"AP"}]
Here code to get data===>
const [states, setStates] = useState([]);
useEffect(() => {
handleGetStates()
}, []);
const handleGetStates = async () => {
let values = {
url: `url`,
method: 'get',
}
try {
const response = await axios(values)
setStates(response.data)
console.log(response.data,'response');
} catch (error) {
// handle error
console.log(error);
}
};
You can filter on the array returned from the API:
...
const response = await axios( values );
setStates( response.data.filter(state => state.title === 'KARNATAKA' );
// result: [ {"id":3,"title":"KARNATAKA","image":"url","stateCode":"KN"} ]
...
This will loop through the response and only keep states that have a title of "KARNATAKA"
You can use an array filter method
const {data} = await axios(values);
const result = data?.filter(el=>el.stateCode==='KN')

(React + Firestore) Component executing before Context authentication?

I have a problem with one of my components. The problem I think I have is that my component executes before my user context stores the currentUser. My code only works when doing a hot reload.
The watchlist component gets all the values from the watchlist array where the document matches the currentUser.uid.
UserContext.js:
const [currentUser, setCurrentUser] = useState(null)
const [watchlist, setWatchlist] = useState(null)
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(user => {
setCurrentUser(user)
})
return unsubscribe
}, [])
const getWatchlist = async () => {
const userRef = await getDoc(doc(db, 'users', currentUser.uid))
setWatchlist(userRef.data().watchlist)
console.log(userRef.data().watchlist)
}
These values are the ids of objects I then GET from an API, these are then pushed to the watchlistData array.
CryptoContext.js
export const getWatchlistData = async (list) => {
const watchlistData = []
for (const item of list) {
const result = await axios.get(
`${coingecko}/coins/${item}`
)
watchlistData.push(result.data)
}
return watchlistData
}
And this is how my Watchlist component code currently looks.
WatchlistItems.jsx
const { watchlist, getWatchlist, currentUser } = useContext(UserContext)
const { dispatch } = useContext(CryptoContext)
useEffect(() => {
if (currentUser) {
dispatch({type: 'SET_LOADING'})
const getWatchlistDataFromAPI = async () => {
await getWatchlist()
const watchlistData = await getWatchlistData(watchlist)
dispatch({type: 'GET_WATCHLIST', payload: watchlistData})
console.log(watchlistData)
}
getWatchlistDataFromAPI()
}
}, [currentUser])
If I refresh the page I get "Uncaught (in promise) TypeError: the list is not iterable", but if I do a hot reload, watchlist, and watchlistData both console.log with the correct data.
This is my first post and so please let me know if I've left anything out.
Thank you in advance for any help :)

Put results of a Get request from axios to an array

I don't understand a very simple task, I made a request from a API with axios in react.
If I console log the res.data, is like 170 result of single objects on my console.
I need to convert all these result in a single array of objects.
It's a basic task but I don't understand how to do it.
The application is a Trello Clone.
I have a variable called board that has all the data and with this list request, I grab all the column the the trello and append to ListObjects [] in newBoardData (it's a clone of board)
Here is my code:
//Get Request
const getList = async (id) => {
try {
return await axios.get(`${ENDPOINT}/lists/${id}`);
} catch (err) {
console.log(err);
}
};
const [loading, setLoading] = useState(false);
// Use Effect for grab the data with the listId
useEffect(() => {
(async () => {
setLoading(true);
const res = await (getList(listId));
//Loading up the listObjects
const oldList = board.board.listObjects
const newList = []
const payload = res.data;
//Adding all the old values to the new list (except for the current payload id)
for(let obj of oldList){
if(obj._id !== payload._id) newList.push(obj)
}
//Adding the current payload id
newList.push(payload)
const data = {
...board,
board: {...board.board, listObjects: newList}
};
setList(res.data);
// Here I put the data objects with the new ListObjects Array
setBoardNew(data);
setLoading(false);
})();
}, []);
Here is the console log of the get request res.data:
console.log of res.data
here is the board object:
board object
You can saw that there is a spam of result with the current res.data in ListObjects
I'think it make a request for every card in every list.
thank you very much!
UPDATE:
I will explain how the app works:
I have a file called Board.js, where I make this call (in the console log I have two call if I have two columns):
try {
return await axios.get(`${ENDPOINT}/boards/${id}`);
} catch (err) {
console.log(err);
}
};
useEffect(() => {
(async () => {
setLoading(true);
const res = await (getUserBoard(match.params.id));
if (res) {
axios.defaults.headers.common['boardId'] = match.params.id;
} else {
delete axios.defaults.headers.common['boardId'];
}
const payload = { ...res.data, listObjects: [], cardObjects: [] };
const data = {
...state,
board: { ...state.board, ...payload },
};
setBoardData(data);
setLoading(false);
})();
}, []);
Then I send the props data to the file List.js
{board.board.lists.map((listId, index) => (
<List key={listId} listId={listId} index={index} board={board} />
The list file send the data to
card.js
{list.cards.map((cardId, index) => (
<Card key={cardId} cardId={cardId} list={list} index={index} board={boardNew} />
The logic is: There is the board(board.js), in the board there are the lists (column)(list.js) in the lists there are the cards (card.js)
I hope it's more clear.
simple use this approach to add your new id value into the array state.
this.setState({ myArray: [...this.state.myArray, 'new value'] }) //simple value
this.setState({ myArray: [...this.state.myArray, ...[1,2,3] ] }) //another array

Using promise.all on an array of axios requests is returning all of the responses in the last object instead of spreading them out

Each object should have around 250 arrays in it, but for some reason, each of the objects has a single array except for the last one, which has 1250.
How can I spread out the responses so I can access each one individually?
const [coinData, setCoinData] = useState();
const [loading, setLoading] = useState(true);
useEffect(() => {
createLocalStorage();
let existingLocalStorage = JSON.parse(localStorage.getItem('items'));
const fetchData = async () => {
const data = await Promise.all(
existingLocalStorage.map(obj =>
coinGecko.get(`/coins/${obj[0].coin}/market_chart/`, {
params: {
vs_currency: 'usd',
days: obj[0].time
}
}),
)
);
setCoinData(data);
setLoading(false);
};
fetchData();
}, []);
Here's the response:
response
I'm using create-react-app, and testing with console.log in the browser
I was sending the times as strings ('day', 'week', 'month', 'year', 'max') I totally forgot I needed to convert them to number values. Since max was the only acceptable parameter, that's the only one that returned the response I was looking for
Try calling your method like below-
import axios from 'axios';
useEffect(() => {
createLocalStorage();
let existingLocalStorage = JSON.parse(localStorage.getItem('charts'));
const fetchData = async () => {
await axios.all([api1, api2]).then(axios.spread((...responses) => {
const resp1 = responses[0]
const resp2 = responses[1]
// use the results
})).catch(errors => {
// errors.
})
}
fetchData();
}, []);

Categories

Resources