Basically what I want is that if a document doesn't exist then create a new one (it works fine now) but if a document does exist, push a new object to the existing array.
I was able to get data from documents and console.log them, but don't know how to push new ones to the existing document.
My FB structure looks like this:
favorites
someUserID
Videos [
0: {
name: SomeName
url: SomeUrl
},
/* I would like to push new objects like this: */
1: {
name: data.name
url: data.url
},
]
This is my current code:
const { user } = UserAuth();
const UserID = user.uid;
const favoritesRef = doc(db, "favorites", UserID);
const test = async (data) => {
try {
await runTransaction(db, async (transaction) => {
const sfDoc = await transaction.get(favoritesRef);
if (!sfDoc.exists()) {
setDoc(favoritesRef, {
Videos: [{name: data.name}]
});
}
/* I got my document content here */
const newFavorites = await getDoc(favoritesRef);
console.log("Document data:", newFavorites.data());
/* And would like to push new Data here */
transaction.update(favoritesRef, { name: data.name});
});
console.log("Transaction successfully committed!");
} catch (e) {
console.log("Transaction failed: ", e);
}
}
To update the array Firestore now has a function that allows you to update an array without writing your code again:
Update elements in an array
If your document contains an array field, you can use arrayUnion()
and arrayRemove() to add and remove elements. arrayUnion() adds
elements to an array but only elements not already present.
arrayRemove() removes all instances of each given element.
import { doc, updateDoc, arrayUnion, arrayRemove } from "firebase/firestore";
const washingtonRef = doc(db, "cities", "DC");
// Atomically add a new region to the "regions" array field.
await updateDoc(washingtonRef, {
regions: arrayUnion("greater_virginia")
});
// Atomically remove a region from the "regions" array field.
await updateDoc(washingtonRef, {
regions: arrayRemove("east_coast")
});
Not sure if this helps but what i usually do is:
I'm adding every user that signs in to an array.
const snapshot = await getDoc(doc(db, "allUsers", "list"));
const currentUsers = snapshot.data().users;
await setDoc(doc(db, "allUsers", "list"), {
users: [...currentUsers, { name, uid: userId, avatar: photo }],
});
I first get the items that exist in the list, and them i create a new one that has every previous item and the ones i'm adding. The currentUsers is the current list in that caso. Maybe you should try thist instead of Videos: [{name: data.name}]
setDoc(favoritesRef, {
Videos: [...currentVideos, {name: data.name}]
})
I just figured it out like this:
const { user } = UserAuth();
const UserID = user.uid
const favoritesRef = doc(db, "favorites", UserID)
const test = async (data) => {
try {
await runTransaction(db, async (transaction) => {
const sfDoc = await transaction.get(favoritesRef);
if (!sfDoc.exists()) {
await setDoc(favoritesRef, {
favs: [
{
name: data.name,
ytb: data.ytb,
url: data.url
}]})
}
const doesExists = sfDoc.data().favs.some((fav) => fav.name === data.name)
console.log(doesExists)
if (doesExists === true)
{
console.log("AlreadyExist")
}
else {
const currentData = sfDoc.data().favs
transaction.update(favoritesRef, {
favs: [...currentData,
{
name: data.name,
ytb: data.ytb,
url: data.url
}]}
)}
});
console.log("Transaction successfully committed!");
} catch (e) {
console.log("Transaction failed: ", e);
}
}
Related
I created a collection "user", nd then created doc as a current logged in user ui (you can see in screenshot). Now I want to add todo in that todo[] array. How can i do this using latest version of firebase v9?
I tried, but getting some error. Please check AddTodo() method below.
async register() {
createUserWithEmailAndPassword(auth, this.email, this.password)
.then((userCredential) => {
const currentUserUid = userCredential.user.uid;
return setDoc(doc(db, "users", currentUserUid), {
// add any additional user data here
name: "fullName",
email: "email",
todo: [],
});
})
.then(() => {
// User registration and document creation successful
})
.catch((error) => {
console.log(error.message);
});
},
async addTodo(){
if (this.newTodo != "" && this.newTask != "") {
const currentUser = auth.currentUser;
const currentUserUid = currentUser.uid;
await addDoc(collection(db, currentUserUid), [
{
title: this.newTodo,
task: this.newTask,
},
]);
}
}
Your second code snippet tries to add a new document to a collection named after the user's UID. That does not match the screenshot you have, which shows a document named after the UID in a collection names users.
To update the latter document, adding a task to the tasks array field:
async addTodo(){
if (this.newTodo != "" && this.newTask != "") {
const currentUser = auth.currentUser;
const currentUserUid = currentUser.uid;
await updateDoc(doc(db, 'users', currentUserUid), {
tasks: arrayUnion([
title: this.newTodo,
task: this.newTask,
]},
]);
}
}
Also see the Firebase documentation on adding and removing elements on an array field.
I have a collection called Users. Inside of that collection each user is added with his own ID etc. Inside of a Users Doc you have a sub collection called skin_collection and inside of that fields that are of type Reference which points to another collection and a document inside of that.
So as an example (sorry for formatting):
Users
Document: 123
skin_collection
Document: 1
Field 1: 'knives/1WnPRlqLPe8Y12YXtm8T'
I'd like to get that referenced docs data so I can use it on the page. Currently I have this useEffect running on the page:
useEffect(() => {
if (user) {
const getData = async () => {
const userRef = doc(db, "Users", user.uid);
const docSnap = await getDoc(userRef);
if (docSnap.exists()) {
console.log("Document data:", docSnap.data());
setUserData(docSnap.data());
const parentDocRef = doc(db, "Users", user.uid);
const skinCollectionRef = collection(parentDocRef, "skin_collection");
skinCollectionRef.onSnapshot((snapshot) => {
snapshot.forEach(async (doc) => {
const docData = doc.data();
Object.keys(docData).forEach(async (key) => {
if (docData[key] && docData[key].isEqual) {
console.log(`${key} is a reference field`);
const referencedDocSnapshot = await docData[key].get();
if (referencedDocSnapshot.exists) {
console.log(`${key} data:`, referencedDocSnapshot.data());
} else {
console.log(`${key} does not exist`);
}
}
});
});
});
} else {
console.log("No such document!");
}
};
getData();
}
}, [user]);
It uses the current logged in user ID to reference its document. I want to get all the data from the subcollection and then those referenced documents's data but it seems Im struggling to get that going.
I am trying to remove an object from array in in firestore, but encountered an obstacle what are the requirement or the reference to do the removal ? does one key value in the object sufficient to do the remove or should the object by identical to the one that is getting removed ?
const deleteWeek = async () => {
const docRef = doc(db, 'Weeks', id);
await updateDoc(docRef, {
weeks: arrayRemove({
weekId: '7518005f-7b10-44b6-8e0a-5e41081ee064',
}),
});
};
deleteWeek();
}
however week in data base looks like this
{name ,"Week 2"
days : [/*data all kinds*/]
weekId : "7518005f-7b10-44b6-8e0a-5e41081ee064"}
If it's an array of object, then you need to know the whole object to use arrayRemove() For example, if the a document looks like this:
{
...data
weeks: [
{
name: "Week 2",
days: [/*data all kinds*/]
weekId: "7518005f-7b10-44b6-8e0a-5e41081ee064"}
}
]
}
You'll have to pass the entire week object in arrayRemove(). It might be better to store such data in sub-collections instead so you can query/delete a specific one.
Since there is no function in firestore to delete only a element in array, you need to make arrayRemove refer to the same object you want to delete, then create a new object and insert it with arrayUnion method
in my case, i use to below
const { leave,date,email } = req.body;
const attendanceRef = admin.firestore().collection('Attendance').doc(`${email}`);
const attendanceData = await attendanceRef.get();
const attendanceRecord = attendanceData.data().attendance;
const removeTarget = attendanceRecord.find((item) => item.date === date);
await attendanceRef.update({
attendance: admin.firestore.FieldValue.arrayRemove(removeTarget),
})
const obj = {
...removeTarget,
"leave": leave,
}
await attendanceRef.set({
attendance: admin.firestore.FieldValue.arrayUnion(obj),
},{ merge: true })
const newAttendance = await attendanceRef.get();
const newAttendanceRecord = newAttendance.data().attendance;
return await res.json({
message: '퇴근시간이 저장되었습니다.',
attendance:newAttendanceRecord
});
after update, it maybe if error occured.
if error occured, you need all working cancel.
this case, you may want to use batch method
const admin = require('firebase-admin');
module.exports = async function(req,res) {
const { leave,date,email } = req.body;
const batch = admin.firestore().batch();
const attendanceRef = admin.firestore().collection('Attendance').doc(`${email}`);
const attendanceData = await attendanceRef.get();
const attendanceRecord = attendanceData.data().attendance;
const removeTarget = attendanceRecord.find((item) => item.date === date);
// await attendanceRef.update({
// attendance: admin.firestore.FieldValue.arrayRemove(removeTarget),
// })
batch.update(
attendanceRef,{ attendance: admin.firestore.FieldValue.arrayRemove(removeTarget) }
)
const obj = {
...removeTarget,
"leave": leave,
}
// await attendanceRef.set({
// attendance: admin.firestore.FieldValue.arrayUnion(obj),
// },{ merge: true })
batch.set(
attendanceRef, { attendance: admin.firestore.FieldValue.arrayUnion(obj) },{ merge: true }
)
await batch.commit();
const newAttendance = await attendanceRef.get();
const newAttendanceRecord = newAttendance.data().attendance;
return await res.json({message: '퇴근시간이 저장되었습니다.',attendance:newAttendanceRecord});
}
hope help this for you
What i have set up for my firestore database is one collection called 'funkoPops'. That has documents that are genres of funkoPops, with an array of funkoData that holds all pops for that genre. it looks like this below
I should also note, that the collection funkoPops has hundreds of documents of 'genres' which is basically the funko pop series with the sub collections of funkoData that I web scraped and now need to be able to search through the array field of 'funkoData' to match the name field with the given search parameter.
collection: funkoPops => document: 2014 Funko Pop Marvel Thor Series => fields: funkoData: [
{
image: "string to hold image",
name: "Loki - with helmet",
number: "36"
},
{
image: "string to hold image",
name: "Black and White Loki with Helmet - hot topic exsclusive",
number: "36"
},
{
etc...
}
So how could i run a query in firestore to be able to search in collection('funkoPops'), search through the document fields for name.
I have the ability to search for genres like so, which gives the genre back and the document with the array of data below:
const getFunkoPopGenre = async (req, res, next) => {
try {
console.log(req.params);
const genre = req.params.genre;
const funkoPop = await firestore.collection("funkoPops").doc(genre);
const data = await funkoPop.get();
if (!data.exists) {
res.status(404).send("No Funko Pop found with that search parameter");
} else {
res.send(data.data());
}
} catch (error) {
res.status(400).send(error.message);
}
};
what i am trying to use to search by the field name is below and returns an empty obj:
const getFunkoPopName = async (req, res, next) => {
try {
const name = req.params.name;
console.log({ name });
const funkoPop = await firestore
.collection("funkoPops")
.whereEqualTo("genre", name);
const data = await funkoPop.get();
console.log(data);
res.send(data.data());
} catch (error) {
res.status(400).send(error);
}
};
Any help would be great, thanks!
So the way i went about answering this as it seems from top comment and researching a little more on firebase, you do you have to match a full string to search using firebase queries. Instead, I query all docs in the collection, add that to an array and then forEach() each funkoData. From there i then create a matchArray and go forEach() thru the new funkoData array i got from the first query. Then inside that forEach() I have a new variable in matches which is filter of the array of data, to match up the data field name with .inlcudes(search param) and then push all the matches into the matchArr and res.send(matchArr). Works for partial of the string as well as .includes() matches full and substring. Not sure if that is the best and most efficient way but I am able to query thru over probably 20k data in 1-2 seconds and find all the matches. Code looks like this
try {
const query = req.params.name.trim().toLowerCase();
console.log({ query });
const funkoPops = await firestore.collection("test");
const data = await funkoPops.get();
const funkoArray = [];
if (data.empty) {
res.status(404).send("No Funko Pop records found");
} else {
data.forEach((doc) => {
const funkoObj = new FunkoPop(doc.data().genre, doc.data().funkoData);
funkoArray.push(funkoObj);
});
const matchArr = [];
funkoArray.forEach((funko) => {
const genre = funko.genre;
const funkoData = funko.funkoData;
const matches = funkoData.filter((data) =>
data.name.toLowerCase().includes(query)
);
if (Object.keys(matches).length > 0) {
matchArr.push({
matches,
genre,
});
}
});
if (matchArr.length === 0) {
res.status(404).send(`No Funko Pops found for search: ${query}`);
} else {
res.send(matchArr);
}
}
} catch (error) {
res.status(400).send(error.message);
}
with a little bit of tweaking, i am able to search for any field in my database and match it with full string and substring as well.
update
ended up just combining genre, name, and number searches into one function so that whenver someone searches, the query param is used for all 3 searches at once and will give back data on all 3 searches as an object so that we can do whatever we like in front end:
const getFunkoPopQuery = async (req, res) => {
try {
console.log(req.params);
const query = req.params.query.trim().toLowerCase();
const funkoPops = await firestore.collection("test");
const data = await funkoPops.get();
const funkoArr = [];
if (data.empty) {
res.status(404).send("No Funko Pop records exsist");
} else {
data.forEach((doc) => {
const funkoObj = new FunkoPop(doc.data().genre, doc.data().funkoData);
funkoArr.push(funkoObj);
});
// genre matching if query is not a number
let genreMatches = [];
if (isNaN(query)) {
genreMatches = funkoArr.filter((funko) =>
funko.genre.toLowerCase().includes(query)
);
}
if (genreMatches.length === 0) {
genreMatches = `No funko pop genres with search: ${query}`;
}
// name & number matching
const objToSearch = {
notNullNameArr: [],
notNullNumbArr: [],
nameMatches: [],
numbMatches: [],
};
funkoArr.forEach((funko) => {
const genre = funko.genre;
if (funko.funkoData) {
const funkoDataArr = funko.funkoData;
funkoDataArr.forEach((data) => {
if (data.name) {
objToSearch.notNullNameArr.push({
funkoData: [data],
genre: genre,
});
}
if (data.number) {
objToSearch.notNullNumbArr.push({
funkoData: [data],
genre: genre,
});
}
});
}
});
// find name that includes query
objToSearch.notNullNameArr.forEach((funko) => {
const genre = funko.genre;
const name = funko.funkoData.filter((data) =>
data.name.toLowerCase().includes(query)
);
if (Object.keys(name).length > 0) {
objToSearch.nameMatches.push({
genre,
name,
});
}
});
// find number that matches query
objToSearch.notNullNumbArr.forEach((funko) => {
const genre = funko.genre;
const number = funko.funkoData.filter((data) => data.number === query);
if (Object.keys(number).length > 0) {
objToSearch.numbMatches.push({
genre,
number,
});
}
});
if (objToSearch.nameMatches.length === 0) {
objToSearch.nameMatches = `No funko pops found with search name: ${query}`;
}
if (objToSearch.numbMatches.length === 0) {
objToSearch.numbMatches = `No funko pop numbers found with search: ${query}`;
}
const searchFinds = {
genre: genreMatches,
name: objToSearch.nameMatches,
number: objToSearch.numbMatches,
};
res.send(searchFinds);
}
} catch (error) {
res.status(400).send(error.message);
}
};
If anyone is well suited in backend and knows more about firestore querying, please let me know!
I am defining the cart variable with await cartsRepo.create({ items: [] });, but I continue to get undefined on cart.
My guess is that the request never resolves, so it never drops into the if conditional, but not sure.
Part of the error was unhandled promise rejection so I threw it all into a try/catch block, but I am still getting undefined on cart.
const express = require("express");
const cartsRepo = require("../repositories/carts");
const router = express.Router();
// Receive a post request to add an item to a cart
router.post("/cart/products", async (req, res) => {
console.log(req.body.productId);
// Figure out the cart!
try {
let cart;
if (!req.session.cartId) {
// // we dont have a cart, we need to create one,
// // and store the cart id on the req.session.cartId property
cart = await cartsRepo.create({ items: [] });
req.session.cartId = cart.id;
} else {
// // We have a cart! Lets get it from the repository
cart = await cartsRepo.getOne(req.session.cartId);
}
const existingItem = cart.items.find(
(item) => item.id === req.body.productId
);
if (existingItem) {
// increment quantity and save cart
existingItem.quantity++;
} else {
// add new product id to items array
cart.items.push({ id: req.body.productId, quantity: 1 });
}
await cartsRepo.update(cart.id, {
items: cart.items,
});
} catch (error) {
console.log(error);
}
res.send("Product added to cart");
});
// Receive a get request to show all items in cart
// Receive a post request to delete an item from a cart
module.exports = router;
The missing piece probably is that I am not using a database but created this repository.js file that where all my data is being stored inside some json files:
const fs = require("fs");
const crypto = require("crypto");
module.exports = class Repository {
constructor(filename) {
if (!filename) {
throw new Error("Creating a repository requires a filename");
}
this.filename = filename;
try {
fs.accessSync(this.filename);
} catch (error) {
fs.writeFileSync(this.filename, "[]");
}
}
async create(attrs) {
attrs.id = this.randomId();
const records = await this.getAll();
records.push(attrs);
await this.writeAll(records);
}
async getAll() {
return JSON.parse(
await fs.promises.readFile(this.filename, {
encoding: "utf8"
})
);
}
async writeAll(records) {
// write the updated 'records' array back to this.filename
await fs.promises.writeFile(
this.filename,
JSON.stringify(records, null, 2)
);
}
randomId() {
return crypto.randomBytes(4).toString("hex");
}
async getOne(id) {
const records = await this.getAll();
return records.find(record => record.id === id);
}
async delete(id) {
const records = await this.getAll();
const filteredRecords = records.filter(record => record.id !== id);
await this.writeAll(filteredRecords);
}
async update(id, attrs) {
const records = await this.getAll();
const record = records.find(record => record.id === id);
if (!record) {
throw new Error(`Record with id ${id} not found`);
}
// record === { email: "test#test.com" }
// attrs === { password: 'mypassword' }
// so attrs is copied over to record object to result in { email: "test#test.com", password: 'mypassword' }
Object.assign(record, attrs);
// take array of records and write it back to JSON file
await this.writeAll(records);
}
async getOneBy(filters) {
const records = await this.getAll();
// iterate through the collection of records - for/of loop because iterating through array
for (let record of records) {
let found = true;
// iterate through all key/value pairs of the filters object - for/in because iterating through object
for (let key in filters) {
// receive every key inside the object and can look at the value inside of object with filters[key]
// it means email or password at filters is the same as email password on record
if (record[key] !== filters[key]) {
// if email and password between filters and record do not match then...
found = false;
}
}
// record was found because filters object key/value pairs and record are same
if (found) {
return record;
}
}
}
};
Just one thing. If you are passing an empty array of items, what is that you are excepting from nothing.
cart = await cartsRepo.create({ items: [] });
And last. What kind of dB you are using?
epascarello mentioned that maybe cartsRepo is not returning what I expect which got me thinking into looking at the repository.js file and sure enough the problem was that I forgot to add a return attrs; to the repository.js file:
async create(attrs) {
attrs.id = this.randomId();
const records = await this.getAll();
records.push(attrs);
await this.writeAll(records);
return attrs;
}