So I'm having a problem that I can't think else how can I add another collection in the parent/first collection let say the diagram should something look like this
owner-item:
group-item: [],
single-item: []
Let say in firebase-collection.js it look something like this
import { collection } from 'firebase/firestore'
import { db } from './firebase'
export const owneritemsRef = collection(db,'owner-items')
And then now I'm exporting now the collection by something like this
let uploadUserStorageTask = uploadBytesResumable(list[i],pictures[j].data)
....
},(err) => {
console.log(err)
},() => {
getDownloadURL(uploadUserStorageTask.snapshot.ref)
.then(async (downloadURL) => {
await setDoc(doc(db,`owner-items`,`${currentUser?.email}-${uid}`),{
creator:username,name:name,img:downloadURL,email:currentUser?.email
})
.then( res => {})
.catch(err => {})
})
})
but because I have group-images I want them to set in a group-item collection but I was thinknig await setDoc or something else I should added to make it like a group-item collection in the owner-item parent collection but how can I do it?
I search something related to it and it something like this maybe? LINK...but I want setDoc because I can change my document_id
UPDATE
It is something like this....
LINK
import { doc } from "firebase/firestore";
const messageRef = doc(db, "rooms", "roomA", "messages", "message1");
I'm sorry its about document inside of collection...let say
await setDoc(..., {
group_item: {},
single_item: {},
}
based on my old snippet just add this new thing... Is this the righht way to do it?
or something like subcollection you know what I mean.
UPDATE 2
let say I have owner-items
await setDoc(doc(db,`owner-items`,`${currentUser?.email}-${uid}`),{
group_items:[{
id:1,
img:file-image
}],
single_item:[{
id:1,
img:file-image
}]
})
If I understand correctly your answer and comments, the following, using a batched write, should do the trick.
import { writeBatch, doc } from "firebase/firestore";
const batch = writeBatch(db);
const parentDocRef = doc(db, `owner-items`, `${currentUser?.email}-${uid}`);
batch.set(parentDocRef, {
single_item: [{
id: 1,
img: file - image
}]
});
const group_items = [
{
id: 1,
img: file - image
},
{...}
];
group_items.forEach(elem => {
const groupItemDocRef = doc(db, `owner-items`, `${currentUser?.email}-${uid}`, 'group-items');
batch.set(groupItemDocRef, {
item: elem
});
});
await batch.commit();
Note that a batched write can contain up to 500 operations, so your group_items array shall have maximum 499 elements.
Related
Situation
I need to form a comments timeline which looks like this,
Yet the information comes from 2 different APIs,
API Format
1. Comments API, an array of comments by chronological order
[
{
comment: 'Hello, how's your day?',
userId: '001',
timestamp: '1670548338131'
},
{
comment: 'Pretty good!',
userId: '002',
timestamp: '1670548338151'
},
{
comment: 'Want to hang out later?',
userId: '001',
timestamp: '1670548338171'
},
...
]
2. User info API, 1 user info per search
{
userId: '001',
userName: 'Ben',
userProfileUrl: 'https://www.photo.com/001'
}
Questions
What's the better way to call API 2 mutiple times?
What's the better way to form the render array? (Want the highest efficiency)
Although modifying the backend data model would be ideal, I'm under some constraints that I can only manipulate data on the frontend.
Initial Idea
Fetch comments list first, use a Set to collect unique userIds
Use Promise.all to fetch API 2 parallelly with all the userIds within the Set
Form a dictionary (map) to lookup user info by userId
Iterate the list from step 1, fill in user info one by one, and produce a new list
Set the final result list into useState state
Render
const getComments = () => axios.get('API_1')
const getUser = (userId) => axios.get(`API_2/${userId}`)
const [renderList, setRenderList] = React.useState([])
const initComments = async () => {
try {
const res = await getComments()
const rawComments = res.data
const usersSet = new Set(rawComments.map(c => c.userId))
const promises = [...usersSet].map(u => getUser(u))
// Here's the question 1, is it viable?
const usersInfo = await Promise.all(promises)
const usersMap = usersInfo.reduce((prev, curr) => {
return {
...prev,
[curr.data.userId]: curr.data,
}
}, {})
// Here's the question 2, is it the most efficient way?
const finalList = rawComments.map(c => {
return {
...c,
userName: usersMap[c.userId].userName,
userProfileUrl: usersMap[c.userId].userProfileUrl,
}
})
setRenderList(finalList)
} catch(err) {}
}
React.useEffect(() => {
initComments()
}, [])
return renderList.map(item => <>{/* Do the render here */}</>)
Context
I'm trying to create a document list view with a filter sidebar. The documents are stored in Firestore and I'm using Nuxt with Vuex to fetch and store the documents.
export const getters = {
plants: (state) => state.plants
}
export const mutations = {
SET_PLANTS(state, payload) {
state.plants = payload
}
}
export const actions = {
async fetchPlants({ commit }, queryArray) {
const docsRef = collection(db, 'plants')
let snapshot;
if (queryArray) {
const q = query(docsRef, where(...queryArray))
snapshot = await getDocs(q)
} else {
snapshot = await getDocs(docsRef)
}
const results = []
snapshot.forEach((doc) => {
results.push({ ...doc.data(), id: doc.id })
})
commit('SET_PLANTS', results)
}
}
Then, in my ListView component I fetch the data:
export default {
async fetch({ store }) {
try {
await store.dispatch('plants/fetchPlants')
} catch (err) {
console.log(err)
}
},
computed: {
plants() {
return this.$store.getters['plants/plants']
}
}
}
And use a simple v-for loop to output the data:
<div v-for="plant in plants" :key="plant.id">
<h3>{{ plant.name }}</h3>
<p>{{ plant.description }}</p>
<span v-for="(value, index) in plant.properties" :key="index">
{{ value }}
</span>
</div>
Notice the nested v-for loop that takes in the plant's properties. These consist of stuff like 'Needs little water', 'Needs little sun' etc.
The question
I would like to create a filter sidebar in the ListView component that allows the user to filter the plants based on their properties using a checkbox for each of them.
As you might have seen, I already have a queryArray parameter setup inside the fetchPlants action:
if (queryArray) {
const q = query(docsRef, where(...queryArray))
snapshot = await getDocs(q)
}
This parameter could look something like this: ['water', '==', 'Needs little water']
However, this setup only allows for 1 filter to be applied. How do I properly chain multiple where() clauses together using checkboxes?
If your queryArray looks like this:
const queryArray = [
['water', '==', 'Needs little water'],
['light', '==', 'Needs little sunlight'],
]
Then you'll have to add a where() for each field as shown below:
const q = query(docsRef, ...queryArray.map((item) => where(item[0], item[1], item[2])))
setup() {
const { orders, orders_error, load_orders, profits } = getOrders()
load_orders()
console.log('ARRAY', profits)
let new_series = [{
name: 'series1',
data: profits.value
}]
return { new_series, orders, load_orders, orders_error, profits }
And this is the .js exported function:
import { ref } from 'vue'
import { projectFirestore, projectAuth } from '../firebase/config'
//import { ref } from '#vue/composition-api'
const getOrders = () => {
const user = projectAuth.currentUser.uid
let orders = ref([])
let profits = ref([])
let profit = 0
const orders_error = ref('')
const load_orders = async () => {
try {
projectFirestore.collection('users')
.doc(user)
.collection('orders')
.doc('845thfdkdnefnt4grirg')
.collection('profits')
.onSnapshot(async (snap) => {
// In this implementation we only expect one active or trialing subscription to exist.
let docs = snap.docs.map(doc => {
return { ...doc.data(), id: doc.id }
})
orders.value = docs
let last = 0
orders.value.forEach(element => {
console.log('ELEMENT', element.profit_cash)
profit = last + element.profit_cash
last = profit
profits.value.push(profit)
//orders.push(element.profit_cash)
})
//console.log('ARR', profits.value)
});
}
catch (err) {
orders_error.value = err.message
console.log(orders_error.value)
}
}
load_errors()
return { orders, orders_error, load_errors, profits }
}
export default getOrders
I'm able to print the profits array correctly between template tags but I can't inside the setup() function.
I just receive an object and I can't access to the array nested into it. Basically I need to set profits array inside new_series to plot cumulative profits in apexchart.
This is my DOM printing profits.value from the component:
enter image description here
In your console.log('ARRAY', profits), you don't access profits.value, whereas you do do that everywhere else. The template understands how to access the value automatically when you return just profits from your setup function.
const profitsValue = profits.value;
console.log('ARRAY', profitsValue);
You can then use that value in your series and keep your current return statement from setup()
Hi I'm currently blocked because I can't get all records from a collection with references values.
I would like to get all records from collection events (it works) but when I wanna merge the category information associated with categoryId my code doesn't work anymore.
Events collection
Categories collection
export const getEventsRequest = async () => {
const output = [];
const data = await firebase.firestore().collection('events').get();
data.forEach(async (doc) => {
const {
name,
address,
city,
duration,
level,
startDate,
maxPeople,
categoryId,
} = doc.data();
const { name: categoryName, color } = (
await firebase.firestore().collection('categories').doc(categoryId).get()
).data();
output.push({
name,
address,
city,
duration,
level,
startDate,
maxPeople,
category: { name: categoryName, color },
});
});
return output;
};
Example testing in a React Native project
const [events, setEvents] = useState([]);
const [isEventsLoading, setIsEventsLoading] = useState(false);
const getEvents = async () => {
setEvents([]);
setIsEventsLoading(true);
try {
const evts = await getEventsRequest();
setEvents(evts);
setIsEventsLoading(false);
} catch (e) {
console.error(e);
}
};
useEffect(() => {
getEvents();
}, []);
console.log('events', events);
Output
events Array []
Expected
events Array [
{
name : "blabla",
address: "blabla",
city: "blabla",
duration: 60,
level: "hard",
startDate: "13/04/2021",
maxPeople: 7,
category: {
name: "Football",
color: "#fff"
},
},
// ...
]
I don't know if there is a simpler method to retrieve this kind of data (for example there is populate method on mongo DB).
Thank you in advance for your answers.
When you use CollectionReference#get, it returns a Promise containing a QuerySnapshot object. The forEach method on this class is not Promise/async-compatible which is why your code stops working as you expect.
What you can do, is use QuerySnapshot#docs to get an array of the documents in the collection, then create a Promise-returning function that processes each document and then use it with Promise.all to return the array of processed documents.
In it's simplest form, it would look like this:
async function getDocuments() {
const querySnapshot = await firebase.firestore()
.collection("someCollection")
.get();
const promiseArray = querySnapshot.docs
.map(async (doc) => {
/* do some async work */
return doc.data();
});
return Promise.all(promiseArray);
}
Applying it to your code gives:
export const getEventsRequest = async () => {
const querySnapshot = await firebase.firestore()
.collection('events')
.get();
const dataPromiseArray = querySnapshot.docs
.map(async (doc) => {
const {
name,
address,
city,
duration,
level,
startDate,
maxPeople,
categoryId,
} = doc.data();
const { name: categoryName, color } = (
await firebase.firestore().collection('categories').doc(categoryId).get()
).data();
return {
name,
address,
city,
duration,
level,
startDate,
maxPeople,
category: { name: categoryName, color },
};
});
// wait for each promise to complete, returning the output data array
return Promise.all(dataPromiseArray);
};
Hi all thanks for looking to my question,
I would like to delete a child referenced in a parent
here is the structure:
const parentSchema: = new Schema({
name: String,
child: { type: mongoose.Schema.Types.ObjectId, ref:'Child' },
})
const childSchema: = new Schema({
name: String,
})
the child is saved to its own child collection and the parent contains its reference.
my approach for this is as follow:
parentSchema.statics.deleteByID = async function (id: string) {
try {
const parent = await this.findOne({ id })
const child = await Child.findOne({_id: parent.child })
const childDel = child && await child.remove()
const parentDel = await parent.remove()
console.log(parent, child, childDel, parentDel)
} catch(err) {
throw new Error(err)
}
}
this works just fine, i wanted to know if this is the best approach.
I don't think if mongoose has this feature built-in.
The best you can do is to create a remove middleware as described here:
By the way, to make your existing code shorter, you can use findByIdAndDelete. It returns the deleted document, so with the following code 2 database hits make the job:
const parentDel = await Parent.findByIdAndDelete(id);
const childDel = await Child.deleteOne({_id: parentDel.child});
console.log(parentDel, childDel);
parentDel will look like this:
{
"_id": "5de0114ad068f335b480925a",
"name": "Parent 1",
"child": "5de01144d068f335b4809259",
"__v": 0
}
And childDel will look like this:
{
"n": 1,
"ok": 1,
"deletedCount": 1
}
I think this is the best approach for my problem, hope it helps anyone.
My problem was in thinking that the pre('remove') hook would be triggering on Class call, but it is called only on instance.
So rather than Parent.deleteOne(), i first find the instance i want to delete with findOne(), and then trigger the pre('remove') with parent.remove(), and delete necessary childs...
Here is an example:
parentSchema.pre<ParentDocument>('remove', async function() {
try {
if (this.child01) {
await Child01.deleteOne({ _id: this.child01 })
}
if (this.child02) {
await Child02.deleteOne({ _id: this.child02 })
}
} catch(err) {
throw new Error(err)
}
})
parentSchema.statics.deleteByID = async function (id: string) {
try {
const parent = await this.findOne({ id })
return !!(parent && await parent.remove())
} catch(err) {
throw new Error(err)
}
}