Error: Query.get failed: First argument must be an object - javascript

Using Firestor on React Native.
I am getting this error:
Error: Query.get failed: First argument must be an object
When I try to fetch User data from other collection(Item), but that would not work.
export const itemsFetch = () => {
return (dispatch) => {
firebase.firestore().collection('items').get()
.then((snapshot) => {
const items = snapshot.docs.map(doc => doc.data());
return items
})
.then((items) => {
const userPromises = items.map((item, itemId) => {
const userId = item.userId;
console.log('userId: ', userId);
return firebase.firestore().collection('users').get(userId)
.then((snapshot) => {
console.log('snapshot:', snapshot);
const user = snapshot.docs.data();
return user;
console.log('user:', user);
})
.then(user => ({...item, user: user, itemId}));
});
dispatch({ type: 'ui/loading/hide' });
return Promise.all(userPromises);
})
};
};
I could get data snapshot but I cannot realize still.

get() doesn't take an argument like this:
return firebase.firestore().collection('users').get(userId)
If you want to fetch a document by ID, you need to build a DocumentReference using doc(), and call get() on it:
return firebase.firestore().collection('users').doc(userId).get()
That will return a promise the yields a single DocumentSnapshot with the contents of that document.

See QuerySnapshot documentation. The .where is optional.
let query = firestore.collection('col').where('foo', '==', 'bar');
query.get().then(querySnapshot => {
let docs = querySnapshot.docs;
for (let doc of docs) {
console.log(`Document found at path: ${doc.ref.path}`);
}
});

Related

Is there a way to make an api call within a map of another api call?

I know the title is quite confusing, I wasn't sure how to word it better. What I am trying to do is to fetch some items, map through those items to display them, but the problem is that one of those items has a value of what needs to be another api call to access it.
This is what I'm trying to do:
First of all I am storing an empty state, which later on becomes the data of the fetched items:
const [data, setData] = useState([]);
I'm using axios to fetch and store the data:
const fetchItems = () => {
axios("https://swapi.dev/api/people")
.then((response) => {
console.log(response.data.results);
const newData = response.data.results.map((item) => ({
name: item.name,
homeworld: () => {
axios.get(item.homeworld).then((response) => {
response.data.results;
});
},
}));
setData(newData);
})
.catch((error) => {
console.log("error", error);
});
};
It works with the name because it's a simple value. However, the homeworld includes a link that needs to be called once again in order to access it, instead of being a simple value like the name in this case. How can I call it and access what values are held within that link, and display them instead of just displaying the url?
I hope this can help you:
const [data,setData] = useState([])
const fetchItems = () => {
axios("https://swapi.dev/api/people")
.then(response => {
console.log(response.data.results);
const { results } = response.data;
for (const item of results) {
axios.get(item.homeworld).then(({data}) => {
setData([...data,{ name: item.name, homeworld: data.results }]);
});
}
})
.catch(error => {
console.log("error", error);
});
};
or with fetch:
const [data,setData] = useState([])
fetch("https://swapi.dev/api/people").then(re=>re.json())
.then(response => {
const newData = []
const { results } = response;
const newData = [];
for (const item of results) {
fetch(item.homeworld).then(re => re.json()).then((data) => {
newData.push({ name: item.name, homeworld: data });
});
}
console.log(newData)
setData(newData)
})
.catch(error => {
console.log("error", error);
});
Use Promise.all()
You can use Promise.all() method to get all the information you need by creating an array of promises by mapping the response.results array with an async function.
This is the code example
const fetchItems = async () => {
const req = await axios.get("https://swapi.dev/api/people");
const response = await req.data;
const allDataPromises = response.results.map(async (item) => {
const itemReq = await axios.get(item.homeworld);
const itemResponse = await itemReq.data;
return {
name: item.name,
homeworld: itemResponse,
};
});
const allData = await Promise.all(allDataPromises);
};
For further information about Promise.all()

How to modify and store data, retrieved from an API call, in Firestore correctly

I retrieve data from an API and want to add new properties(comments:[], likes:'', etc.) each of the map. What I tried is that modifying data on UI side, and it didn't effect Firestore side. Even I modify the data on Firestore side by creating new collection it works until I refresh the app. Can you please show me the correct way how to modify and store the data?
const newsFromApiRef = firestore().collection('topHeadlines');
//Adding data to firestore
const newsOnFirebase = async () => {
await getTopHeadlines('us')
.then((data) => {
newsFromApiRef
.doc('testDocUID')
.set(data)
.then(() => {
console.log('Data added succesfully');
})
.catch((error) => {
console.log(error);
});
})
.catch((error) => {
console.log(error);
});
};
//Getting data from firestore, and added new properties
const getFromFirebase = async () => {
await newsOnFirebase();
var data;
await newsFromApiRef
.doc('testDocUID')
.get()
.then((querySnapshot) => {
const newData = [];
querySnapshot.data().articles.forEach((list) => {
if (list?.comments) {
newData.push({ ...list });
} else {
newData.push({
...list,
comments: [],
});
}
});
data = {
articles: [...newData],
status: querySnapshot.data().status,
totalResults: querySnapshot.data().totalResults,
};
})
.catch((error) => {
console.log(error);
});
return data;
};
If you want to get a document's data from Firestore and add new properties to this document, you need to first query for the document (with the get() method), as you do, and then write back to Firestore, either with the update() or set() methods (which you don't do).
Since you are using async/await, you can do along the following lines:
const snap = await newsFromApiRef.doc('testDocUID').get();
const docData = snap.data();
const newData = [];
docData.articles.forEach((list) => {
if (list?.comments) {
newData.push({ ...list });
} else {
newData.push({
...list,
comments: [],
});
}
});
const data = {
articles: [...newData],
status: docData.status,
totalResults: docData.totalResults,
};
await newsFromApiRef.doc('testDocUID').update(data);
Note that since you read one document, you are actually getting a DocumentSnapshot, and not a QuerySnapshot, hence the renaming in my anwser.

Promise inside a loop inside an async function

I am working on a project using react and firebase and redux and I have some items that did created by a user. I'm storing the id of the user in the item object so i can populate the user later when i get the item to display.
Now I'm trying to get the items and modify them by replacing the user id with the actual info about the user but I have a promises problem. In my code I just get an empty array which mean the modification didn't get resolved before I return the final result.
export const getItems = () => {
return (dispatch, getState, { getFirebase }) => {
const firestore = getFirebase().firestore();
const items = [];
const dbRef = firestore.collection('items').orderBy('createdAt', 'desc').limit(2);
return dbRef
.get()
.then((res) => {
const firstVisible = res.docs[0];
const lastVisible = res.docs[res.docs.length - 1];
async function getData(res) {
/////////////////////////////////////////////// how to finish this code befor jumping to the return line
await res.forEach((doc) => {
firestore
.collection('users')
.doc(doc.data().owner)
.get()
.then((res) => {
items.push({ ...doc.data(), owner: res.data() });
});
});
////////////////////////////////////////////////
return { docs: items, lastVisible, firstVisible };
}
return getData(res);
})
.catch((err) => {
console.log(err);
});
};
};
I don't get exactly what you are trying to do, but I would suggest putting some order to make your code easy to read and work with.
You can use for of to manage async looping. I suggest something like this, disclaimer, I did it at the eye, problably there are some errors, but you can get the idea.
const getAllDocs = function (data) {
let temp = [];
data.forEach(function (doc) {
temp.push(doc.data());
});
return { data: temp };
};
const getDoc = snap => (snap.exists ? { data: snap.data() } : {});
export const getItems = () => {
return async (dispatch, getState, { getFirebase }) => {
const firestore = getFirebase().firestore();
const dbRef = firestore.collection('items').orderBy('createdAt', 'desc').limit(2);
const usersRef = firestore.collection('users');
let temps = [];
const { data: items } = await dbRef.get().then(getAllDocs);
const firstVisible = items[0];
const lastVisible = items[items.length - 1];
for (const item of items) {
const { data: user } = await usersRef.doc(item.owner).get().then(getDoc);
const owner = {
/* whatever this means*/
};
temps.push({ ...user, owner });
}
return { docs: temps, lastVisible, firstVisible };
};
};
The problem is that an array of Promises is not itself a Promise -- so awaiting it will be a no-op.
You can solve this using Promise.all if you want to load them all asynchronously.
const items = await Promise.all(res.map(async (doc) => {
const res = await firestore.collection('users').doc(doc.data().owner).get();
return { ...doc.data(), owner: res.data() };
});
Otherwise you can await in a for loop as suggested in other answers.

Firestore and express, .then chain not stopping when returned res.status(401).json()

So, I'm trying to implement .increment into my API. I need to update a like count more than once per second so that's why I'm trying to update my API to do this. This is the exact code I'm trying to implement:
const postRef = db
.collection('posts')
.doc(postId);
postRef.update({likeCount: admin.firestore.FieldValue.increment(1)});
The code above works perfectly when there is no current like document associated to a user handle, but when there is, the
return res.status(401).json({'Error': `${req.user.userHandle} already liked this post`});
gets hit and returns the error message perfectly. But, the rest of the .then chains get hit, which they shouldn't be.
Here is the old code that works:
exports.likepost = (req, res) => {
const postId = req.body.postId;
db
.collection('posts')
.doc(postId)
.get()
.then((doc) => {
if(!doc.exists){
return res.status(404).json({'Error': `post ID ${postId} not found`});
}
else{
return db.collection('likes')
.where('postId', '==', postId)
.where('userHandle', '==', req.user.userHandle)
.get();
}
})
.then((likeDoc) => {
if(!likeDoc.empty){
return res.status(404).json({'Error': `${req.user.userHandle} already liked this post`});
}
else{
return db
.collection('posts')
.doc(postId)
.get()
}
})
.then((doc) => {
const oldLikeCount = doc.data().likeCount;
const newLikeCount = oldLikeCount + 1;
return db
.collection('posts')
.doc(postId)
.update({
likeCount: newLikeCount
});
})
.then(() => {
const like = {
userHandle: req.user.userHandle,
postId: postId,
likedAt: new Date().toISOString()
};
return db
.collection('likes')
.add(like);
})
.then(() => {
return res.json({message: `Successfully liked ${postId}`});
})
.catch((err) => {
return res.status(500).json({error: err});
});
}
In this code, the res.status(404).json() gets hit AND the rest of the .then chains DON'T execute, which is perfect. But this code uses too many reads and writes as well as it's unable to update a document more than 1 time per second.
Here is the new code that has the bug:
exports.likepost = (req, res) => {
const postId = req.body.postId;
db
.collection('likes')
.where('postId', '==', postId)
.where('userHandle', '==', req.user.userHandle)
.get()
.then((likeDoc) => {
if(!likeDoc.empty){
return res.status(401).json({'Error': `${req.user.userHandle} already liked this post`});
} // It should stop here but it is not.... the code returns the 401 status and the error message so it hits this code, but it doesn't stop the chain
else{
// Update count
const postRef = db
.collection('posts')
.doc(postId);
postRef.update({likeCount: admin.firestore.FieldValue.increment(1)});
}
})
.then(() => {
const like = {
userHandle: req.user.userHandle,
postId: postId,
likedAt: new Date().toISOString()
};
return db
.collection('likes')
.add(like);
})
.then(() => {
return res.json({message: `Successfully liked ${postId}`});
})
.catch((err) => {
console.error(err);
return res.status(500).json({error: err});
});
}
Any help is greatly appreciated!!!
I don't think that stacking 3 or 4 then() in sequence of one another is a best practice. If you changed your code to include the then() inside one another, this kind of unexpected behaviour would be mitigated. Here is what your code could look like if you follow this approach(on your new code version):
exports.likepost = (req, res) => {
const postId = req.body.postId;
db.collection('likes')
.where('postId', '==', postId)
.where('userHandle', '==', req.user.userHandle)
.get()
.then((likeDoc) => {
if(!likeDoc.empty){
return res.status(401).json({'Error': `${req.user.userHandle} already liked this post`});
}
else{
const postRef = db.collection('posts').doc(postId);
postRef.update({likeCount: admin.firestore.FieldValue.increment(1)}).then({
const like = {
userHandle: req.user.userHandle,
postId: postId,
likedAt: new Date().toISOString()
};
db.collection('likes').add(like).then({
return res.json({message: `Successfully liked ${postId}`});
});
});
}
})
.catch((err) => {
console.error(err);
return res.status(500).json({error: err});
});
}
Hope this helps.

How can i return the value of my userID with firebase.auth().onAuthStateChanged(), it currently returns undefined

I'm trying to retrieve the userID of the current user but it returns undefined.
How can i fix this?
getUser = async () => {
let userID
firebase.auth().onAuthStateChanged(user => {
if (user) {
userID = user.uid.toString()
console.log(userID) // return the userID
}
})
return userID
}
export const writeUserData = async (weight, reps, date, pathExercice) => {
let pathID = await getUser()
console.log(pathID) // returns undefined
firebase.database().ref('users/9DLCsvffRRccr5e7YxPulzzjh2n1/' + pathExercice).push({
weight,
reps,
date
}).then((data) => {
// console.log('data ', data)
}).catch((error) => {
console.log('error ', error)
})
}
Your getUser() function uses a callback, which has nothing to do with the async/await feature of JS: the user id is returned before the callback is triggered.
Using the firebase.auth().currentUser is the way to go. Check the docs here: https://firebase.google.com/docs/auth/web/manage-users

Categories

Resources