I'm using the #react-native-firebase wrapper to interact with Firebase's Firestore. I have a function which performs some querying to one of my collections and the intended response should be an Array object containing each found document.
My function currently looks like:
export const backendCall = async => {
let response = []
firestore()
.collection('collection')
.where('id', '==', 1)
.onSnapshot(documentSnapshot => {
documentSnapshot.forEach(x => response.push(x.data()))
})
return response
On my UI, I use a react-native Button component which calls this function onPress:
<Button
title="get"
onPress={() => {
backendCall().then(response => console.log(response))
}}
/>
Using console.log I am able to observe the expected Array object (but with a "value below was evaluated just now" icon. If however, I change the onPress to console.log(JSON.stringify(response)), the Array object is empty.
I'm assuming this has something to do with the async calls but I can't quite figure out how to fix it. Would appreciate some pointers.
You're returning the response without waiting for the result from your firebase query. To wait for the response to arrive before returning, you can use Promise.
export const backendCall = () => {
return new Promise(resolve => {
firestore()
.collection('collection')
.where('id', '==', 1)
.onSnapshot(documentSnapshot => {
const response = []
documentSnapshot.forEach(x => response.push(x.data()))
resolve(response)
})
})
}
You can use Array.map to make the for loop looks nicer.
const response = documentSnapshot.map(x => x.data())
resolve(response)
You can also read docs from a QuerySnapshot:
export const backendCall = async () => {
const qs = firestore().collection('collection').where('id', '==', 1).get()
return qs.docs.map(x => x.data())
}
Related
I have an API called getQuote and a component called QuoteCard. Inside QuoteCard I'm trying to render an array of users that liked a quote. The API works fine, I have tested it, and the code below for getting the users works fine too.
const Post = async (url, body) => {
let res = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"accept": "*/*"
},
body: JSON.stringify(body)
}).then(r => r.json());
return res;
}
const getAllLikes = async () => {
let users = await Post('api/getQuote', {
id: "639e3aff914d4c4f65418a1b"
})
return users
}
console.log(getAllLikes())
The result is working as expected :
However, when trying to map this promise result array to render it onto the page is where I have problems. I try to render like this:
<div>
{getAllLikes().map((user) => (
<p>{user}</p>
))}
</div>
However, I get an error that states:
getAllLikes(...).map is not a function
I don't understand why this is happening. Why can't I map the array? Is it because it's a promise or something?
And if anyone needs to see the getQuote API, here it is:
//Look ma I wrote an API by myself! :D
import clientPromise from "../../lib/mongodb";
const ObjectId = require('mongodb').ObjectId;
import nc from "next-connect";
const app = nc()
app.post(async function getQuote(req, res) {
const client = await clientPromise;
const db = client.db("the-quotes-place");
try {
let quote = await db.collection('quotes').findOne({
_id: new ObjectId(req.body.id)
})
res.status(200).json(JSON.parse(JSON.stringify(quote.likes.by)));
} catch (e) {
res.status(500).json({
message: "Error getting quote",
success: false
})
console.error(e);
}
})
export default app
Thanks for any help!
It is due to the fact that getAllLikes is an async function and thus it returns promise which does not have a map function.
You can either save it in a state variable before using await Or chain it with .then.
Minimal reproducible example which works
const getAllLikes = async () => {
return ['a', 'b']
}
getAllLikes().then((r) => r.map((g) => { console.log(g) }))
Edit: The above code won't work if directly used with jsx since the return of getAllLikes will still be a promise. Solution would be to save it in a state variable and then using it.
I am from Angular and I believe we call pipe on Observables (or Promises). Map can then be called inside the pipe function
observable$ = getAllLikes().pipe(map( user => <p>{user}</p>))
If there is no pipe, I can only think of manually subscribing (which is not a good practice)
sub$ = getAllLikes().subscribe( user => <p>{user}</p>)
// unsub from sub$ appropriately
// We do this from ngOnDestroy in angular
ngOnDestroy() {
this.sub$?.unsubscribe()
}
I'm building a CRUD application using redux toolkit and firestore, and I cannot figure out how to delete an item from firestore, and I really don't know why the following code isn't working. I've tried this in several different ways, and the current error that I'm getting is:
"Cannot use 'in' operator to search for '_delegate' in undefined"
Here's the relevant code from the slice:
export const recipeSlice = createSlice({
name: 'recipesSlice',
initialState: {
recipes: []
},
reducers: {
ADD_RECIPE: (state, action) => {
state.recipes.push(action.payload)
},
DELETE_RECIPE: (state, action) => {
state.recipes = state.recipes.filter((recipe) => recipe.recipeId !== action.payload.recipeId)
}
And here is the thunk that I cannot, for the life of me make work:
export const deleteRecipe = ({recipeId}) => {
return async (dispatch) => {
const q = query(collection(db, "recipes"), where("recipeId", "==", `${recipeId}`));
const querySnapshot = await getDocs(q);
querySnapshot.forEach(async(doc) => {
console.log(doc.id, " => ", doc.data())
await deleteDoc(doc)
});
dispatch(DELETE_RECIPE({recipeId}))
}
}
I didn't use createAsyncThunk because it didn't seem to be a good use case, but I could be wrong.
I've tried firing this function with hard-coded dummy data, and that doesn't help. I have also tried running this code without the 'DELETE_RECIPE' reducer but that doesn't make a difference. I thought that using async/await within the forEach loop on the querySnapshot would work because it's not a typical forEach loop but rather a method in Firestore to iterate over querysnapshot.
The deleteDoc() functions takes DocumentReference of the document and not the snapshot. Also try using Promise.all() or Batched Writes to delete the documents at once instead of using a forEach() loop. Try refactoring the code as shown below:
const querySnapshot = await getDocs(q);
const deletePromises = querySnapshot.docs.map((d) => deleteDoc(d.ref))
await Promise.all(deletePromises)
console.log("Documents deleted")
I'm in react Native, and I have a request that im making using axios, and the request is supposed to return something like this:
Json data
I want to be able to save only the "Products" array in a variable.
This is the snippet of my cod, but it's giving me a 'Possible unhandled promise rejection' and I dont understand why:
const [shoesData, setShoesData] = useState([]);
useEffect(() => {
const getShoesData = async () => {
await axios.get("https://stockx.com/api/browse?productCategory=sneakers&sort=release_date&releaseTime=gte-" + Date.now().toLocaleString() + "&order=ASC&country=FR")
.then(response => {
let products = response.data.map(x => {
return x.products;
});
setShoesData(products);
console.log(products);
})
}
getShoesData();
}, [])
Thanks in advance for your help.
Try and wrap your await instruction with a try catch. One possible error can be that the data you get from the response can, in some cases, not have a products field.
I'm learning how to use fetch and was trying the following syntax:
const [stuff, setStuff] = useState([]);
const request = "link-to-API";
const data = await fetch(request)
.then(response => response.json())
.catch(err => {
console.log(err);
return {} //(or [], or an empty return, or any return at all)
})
setStuff(data.hits)
Then, in the return, I have:
{stuff.map((element) => (
<Thing
title={element.label}
link={element.url}
/>
))}
Thinking I could just render an empty object whenever my fetch fails. Except, this works only when the fetch itself works. React gives me the error
"Objects are not valid as a React child (found: TypeError: Failed to
fetch)."
But I can't find any solution online. How could I handle the errors just by not rendering anything?
(that's not the only part I'm rendering, I just want to render an empty div, not conditionally render that part)
when you use await you can't use then and catch methods
It's important that you use await in async function
let data = null
try{
const response = await fetch(request)
data = response.json();
} catch(err) {
console.log(err);
}
you can try removing the await keyword, as you are using .then
also the datafetching part should be included inside useEffect
const [stuff, setStuff] = useState([]);
const request = "link-to-API";
useEffect( ()=> {
fetch(request)
.then(response => response.json())
.then(data => setStuff(data.hits))
.catch(err => {console.log(err)})
},[])
I'm trying to get multiple data objects from The Movie Database at once using Promise.all. After I loop through all the results of the fetch call, and use .json() on each bit of data, I tried to log it to the console. However, rather than an array of objects with data, I'm getting an array of Promises. Nested in the promises, I can see my data, but I'm clearly missing a step in order to have an array of data objects, instead of just Promises.
What am I missing here?
//store movie API URLs into meaningful variables
const trending = `https://api.themoviedb.org/3/trending/all/day?api_key=${API_KEY}`;
const topRated = `https://api.themoviedb.org/3/movie/top_rated?api_key=${API_KEY}&language=en-US&page=1`;
const nowPlaying = `https://api.themoviedb.org/3/movie/now_playing?api_key=${API_KEY}&language=en-US&page=1`;
const upcoming = `https://api.themoviedb.org/3/movie/upcoming?api_key=${API_KEY}&language=en-US&page=1`;
//create an array of urls to fetch data from
const allMovieURLs = [trending, topRated, nowPlaying, upcoming];
const promiseURLs = allMovieURLs.map(url => fetch(url));
Promise.all(promiseURLs)
.then(responses => responses.map(url => url.json()))
.then(dataArr => console.log(dataArr));
};
Your .then(responses => responses.map(url => url.json())) resolves to an array of Promises, so you need to call Promise.all again if you want to wait for all to resolve:
Promise.all(promiseURLs)
.then(responses => Promise.all(responses.map(url => url.json())))
.then(dataArr => console.log(dataArr));
Or, you might consider using just one Promise.all, and having each URL fetch and the json, that way some items aren't idle in the middle of script execution:
const allMovieURLs = [trending, topRated, nowPlaying, upcoming];
const promiseURLs = allMovieURLs.map(url => fetch(url).then(res => res.json()));
Promise.all(promiseURLs)
.then(dataArr => console.log(dataArr));
try doing it this way
const promiseURLs = allMovieURLs.map(url => fetch(url).then(res => res.json()));
Promise.all(promiseURLs)
.then(responses => responses.forEach(response => { console.log(response)})