How to obtain and display a single document from a list of documents from Firebase Firestore data?
I’m able to grab the collection of documents and display the information but I just want to grab one so I can display it elsewhere without duplicating multiple elements with data.
Firebase example:
DocID: title - “Pizza”
DocID: title - “Pancakes”
DocID: title - “Cereal”
Vue template example:
H1-tag {{display the title of Pizza here}} h1tag
Is there a way to do this without having multiple h1 elements with different titles?
DocID: title - “Pizza” DocID: title - “Pancakes” DocID: title - “Cereal”
One of the options:
Must be an array or convert to an array. And then receive data from it.
items: [
{
title: 'Pizza',
price: ''
},
{
title: 'Pancake',
price: ''
},
{
title: 'Cereal',
price: ''
}
]
<h1>{{items[0].title}}</h1>
vue3
setup () {
const items = ref([])
db.collection('products').onSnapshot((snapshotChange) => {
items.value = []
snapshotChange.forEach((doc) => {
items.value.push({
key: doc.id,
title: doc.data().title,
price: doc.data().price
})
})
})
return {
items
}
}
Related
i need to get user details with post count(Number of posts for today).
const usersWithCount = await prisma.user.findMany({
select: {
_count: {
select: {
posts: {
where: {
createdAt: moment().format("YYYY-MM-DD"),
},
},
recipes: true,
},
},
},
})
You cannot filter in relations count, this is not currently supported by prisma.
Here is the Feature Request for adding filters in relations count.
In your use case you can get filtered relations as described below:
const usersWithCount = await prisma.user.findMany({
select: {
posts: {
where: {
createdAt: moment().format("YYYY-MM-DD"),
},
},
recipes: true
},
});
In the response of the above query you will get posts array with records satisfying your where condition, you can use the array's length as a count.
I have a deeply nested data object that comes back from my API which looks like the JSON below.
I am using Redux toolkit's createSlice to create a slice of a trip
So currently in my createSlice, I want to store an array of trips.
I also want the ability to update a single trip or part of the trip
for example, let's say I want to update a trip item's start date
or, let's say I want to update a trip item's member's name
My questions and concerns:
I currently have all of these entities coming back into the trip createSlice but I am not sure if, once the entities are normalized, should they be separated into separate createSlices? if so, how is this done or is this an anti pattern?
how should nested entities be defined in initialState?
should I define all of my normalized entities in my initalState?
if I do that, how would my reducers look like when I want to update a trip_item or trip_item_member ?
does my normalized data even look "correct"? I have omitted using mergeStrategy between trips_items_members and trip_members which I know I should do but haven't figured out how that works yet or if it's necessary here?
Note:
There is an example in the RTK docs here which shows createSlice being used with 3 separate entities, which originally came from 1 API call. It looks like 3 separate files however it is unclear how data is shared amongst them.
This is how my trip createSlice looks like
/**
* Get trip by ID action
*/
export const getTripByID = createAsyncThunk(
'trips/getTripByID',
async ({ uid }) => {
const response = await findOne(uid)
const normalized = normalize(response, trip)
return normalized.entities
},
)
const tripsAdapter = createEntityAdapter({
selectId: entity => entity.trip_id,
sortComparer: (a, b) => b.start_date.localeCompare(a.start_date),
loading: '',
error: '',
data: [],
})
export const {
selectById: selectTripById,
selectIds: selectTripIds,
selectEntities: selectTripEntities,
selectAll: selectAllTrips,
selectTotal: selectTotalTrips,
} = tripsAdapter.getSelectors(state => state.trip)
const initialState = tripsAdapter.getInitialState()
const tripSlice = createSlice({
name: 'trips',
initialState,
extraReducers: builder => {
builder.addCase(getAllTrips.fulfilled, (state, { payload }) => {
tripsAdapter.upsertMany(state, payload)
state.loading = false
})
builder.addCase(getTripByID.fulfilled, (state, { payload }) => {
console.log('payload', payload)
tripsAdapter.upsertMany(state, payload)
state.loading = false
})
},
})
export default tripSlice.reducer
API response that comes back from await findOne(uid)
{
created_by: "6040c2d1-ea57-43b6-b5f2-58e84b220f4e",
deleted_by: null,
destination: "Valencia",
end_date: "2020-10-04",
start_date: "2020-09-27",
trip_id: "34a620e8-51ff-4572-b466-a950a8ce1c8a",
uid: "14047a5b-2fe5-46c9-b7f2-e9b5d14db05b",
updated_by: null,
trip_items: [
{
destination: "Mezzanine Level Shivaji Stadium Metro Station, Baba Kharak Singh Rd, Hanuman Road Area, Connaught Place, New Delhi, Delhi 110001, India",
end_date: "2020-09-28",
end_time: "2020-09-28T01:20:15.906Z",
note: null,
start_date: "2020-09-28",
start_time: "2020-09-28T01:20:15.906Z",
trip_item_id: "bd775be7-2129-42c0-a231-5a568b0f565d",
trips_items_members: [
{
trip_item_member_id: "76b54a80-4d09-4768-bc5a-4d7e153e66dc",
uid: "4b88f9af-8639-4bb0-93fa-96fe97e03d02",
}
],
uid: "e5f81a6d-1a0d-4456-9d4e-579e80bc27d8",
}
],
trips_members: [
{
trip_member_id: "76b54a80-4d09-4768-bc5a-4d7e153e66dc",
uid: "4b88f9af-8639-4bb0-93fa-96fe97e03d02",
role: "ADMIN"
}
]
}
This is my normalizr schema
const tripItemMember = new schema.Entity(
'trips_items_members',
{},
{ idAttribute: 'trip_item_member_id' },
)
const tripItem = new schema.Entity(
'trips_items',
{
trips_items_members: [tripItemMember],
},
{
idAttribute: 'trip_item_id',
},
)
const tripMember = new schema.Entity(
'trips_members',
{},
{
idAttribute: 'trip_member_id',
},
)
export const trip = new schema.Entity(
'trip',
{
trips_items: [tripItem],
trips_members: [tripMember],
},
{
idAttribute: 'trip_id',
},
)
This is the output from normalizr
trip: {
"34a620e8-51ff-4572-b466-a950a8ce1c8a": {
created_by: "6040c2d1-ea57-43b6-b5f2-58e84b220f4e"
deleted_by: null
destination: "Valencia"
end_date: "2020-10-04"
start_date: "2020-09-27"
trip_id: "34a620e8-51ff-4572-b466-a950a8ce1c8a"
trips_items: ["bd775be7-2129-42c0-a231-5a568b0f565d"]
trips_members: ["76b54a80-4d09-4768-bc5a-4d7e153e66dc"]
uid: "14047a5b-2fe5-46c9-b7f2-e9b5d14db05b"
updated_by: null
}
}
trips_items:{
"0a56da0f-f13b-4c3d-896d-30bccbe48a5a": {
destination: "Mezzanine Level Shivaji Stadium Metro Station"
end_date: "2020-09-28"
end_time: "2020-09-28T01:20:15.906Z"
note: null
start_date: "2020-09-28"
start_time: "2020-09-28T01:20:15.906Z"
trip_item_id: "0a56da0f-f13b-4c3d-896d-30bccbe48a5a"
trips_items_members: []
uid: "25d20a9d-1eb9-4226-926d-4d743aa9d5dc"
}
}
trips_members: {
"76b54a80-4d09-4768-bc5a-4d7e153e66dc": {
role: "ADMIN"
trip_member_id: "76b54a80-4d09-4768-bc5a-4d7e153e66dc"
uid: "4b88f9af-8639-4bb0-93fa-96fe97e03d02"
}
}
Your setup is very much like this detailed example from the redux-toolkit docs. They are fetching articles, but each article comes with embedded users and comments. They define separate slices for each of the three entities.
The comments slice has no actions or reducers of its own, but it uses the extraReducers property to respond to the article received action and store the embedded comments.
const commentsAdapter = createEntityAdapter();
export const slice = createSlice({
name: "comments",
initialState: commentsAdapter.getInitialState(),
reducers: {},
extraReducers: {
[fetchArticle.fulfilled]: (state, action) => {
commentsAdapter.upsertMany(state, action.payload.comments);
}
}
});
The fetchArticle action is "owned" by the article slice, but the action payload contains entities from all three types. All slices receive all actions, so the comments and users are able to respond to this action with their own logic. Each slice doesn't have any effect on what the others can or can't do.
In your case you want to create slices for items and members. Instead of calling upsertMany(state, payload), you want the payload to be keyed by entity type so that you can call upsertMany(state, payload.members).
I'm developing flutter app with FireStore.
I have declared a variable 'total' and List of objects to be filled with some data from collection called 'cart', then upload these data to collection called 'orders'
This code grab the data from collection 'cart':
void submitOrder() async {
List<CartItem> products =[];
double total =0;
CartItem temp= CartItem(customerId:'' ,itemId:'' ,id: '', title: '', quantity:0, price: 0);
DatabaseService().cartCollection.getDocuments().then(
(snapshot) => snapshot.documents.forEach((doc) {
temp = CartItem(
customerId: user.uid,
itemId: doc.data['itemId'].toString(),
id: doc.documentID.toString(),
title: doc.data['itemName'].toString(),
quantity: int.tryParse(doc.data['quantity'].toString()) ,
price: double.tryParse( doc.data['price'].toString()));
total += temp.quantity*temp.price;
print(total); /// This print shows accurate total
products.add(temp);
}
),
);
// Send data to setOrderData in db class to set new doc in order collection
DatabaseService().setOrderData(products, total, user.uid, branchId, 'open');
}
method setOrderData in DB class:
// Add Order
Future setOrderData(List<CartItem> cartProducts, double total, String customerId, String branchId, String status ) async {
final timestamp = DateTime.now();
return await orderCollection.document(uid).setData(
{
'customerId': customerId,
'branchId': branchId,
'status': status ,
'amount': total.toString(), //
'dateTime': timestamp.toIso8601String(),
'products': cartProducts.map((cp) => {
'id': cp.id,
'title': cp.title,
'quantity': cp.quantity,
'price': cp.price,
}).toList(),
});
}
data in FireStore shows that products and total are null?
Link to image:
If anyone can help me out I'd be grateful.
I highly recommend using a code formatter on your code, as it makes it much more likely that people can spot problems like the one you're having. When I reformat the first code block you shared, it becomes:
void submitOrder() async {
List<CartItem> products = [];
double total = 0;
CartItem temp = CartItem(
customerId: '', itemId: '', id: '', title: '', quantity: 0, price: 0);
DatabaseService().cartCollection.getDocuments().then(
(snapshot) => snapshot.documents.forEach((doc) {
temp = CartItem(
customerId: user.uid,
itemId: doc.data['itemId'].toString(),
id: doc.documentID.toString(),
title: doc.data['itemName'].toString(),
quantity: int.tryParse(doc.data['quantity'].toString()),
price: double.tryParse(doc.data['price'].toString()));
total += temp.quantity * temp.price;
print(total);
/// This print shows accurate total
products.add(temp);
}),
);
// Send data to setOrderData in db class to set new doc in order collection
DatabaseService().setOrderData(products, total, user.uid, branchId, 'open');
}
An in this format it's immediately clear to me that you're calling setOrderData before any of the products.add(temp) calls have happened.
This is because data is loaded from Firestore asynchronously. Since this may take some time, your main code (including the return) continues so the UI is not blocked. Then when the data is available, your then callback is invoked.
This means that any code that needs the data from Firestore needs to be inside the then callback. So in your case, the solution could be as simple as moving the call to setOrderData to inside the then:
void submitOrder() async {
List<CartItem> products = [];
double total = 0;
CartItem temp = CartItem(
customerId: '', itemId: '', id: '', title: '', quantity: 0, price: 0);
DatabaseService().cartCollection.getDocuments().then(
(snapshot) => snapshot.documents.forEach((doc) {
temp = CartItem(
customerId: user.uid,
itemId: doc.data['itemId'].toString(),
id: doc.documentID.toString(),
title: doc.data['itemName'].toString(),
quantity: int.tryParse(doc.data['quantity'].toString()),
price: double.tryParse(doc.data['price'].toString()));
total += temp.quantity * temp.price;
print(total);
/// This print shows accurate total
products.add(temp);
}),
// Send data to setOrderData in db class to set new doc in order collection
DatabaseService().setOrderData(products, total, user.uid, branchId, 'open');
);
}
So the things to take away from this:
Always format your code, as it makes it easier for you and others to understand the flow and find problems.
Data is loaded from Firestore (and most cloud APIs) asynchronously, and you can only use the data inside the then() callback, or by using await.
The best Practice to solve such an error is to use try and then catch with the await for each function like the below code
void submitOrder() async {
List<CartItem> products = [];
double total = 0;
CartItem temp = CartItem(
customerId: '', itemId: '', id: '', title: '', quantity: 0, price: 0);
try {
await DatabaseService().cartCollection.getDocuments().then(
(snapshot) => snapshot.documents.forEach((doc) {
temp = CartItem(
customerId: user.uid,
itemId: doc.data['itemId'].toString(),
id: doc.documentID.toString(),
title: doc.data['itemName'].toString(),
quantity: int.tryParse(doc.data['quantity'].toString()),
price: double.tryParse(doc.data['price'].toString()));
total += temp.quantity * temp.price;
print(total);
/// This print shows accurate total
products.add(temp);
}),
);
// Send data to setOrderData in db class to set new doc in order collection
await DatabaseService()
.setOrderData(products, total, user.uid, branchId, 'open');
} catch (error) {
print(error);
}
}
I just want to Publish the relational Data for a Publication to client, but the issue is my Relational Data field is array of ID's of a Different Collection, I tried Different Packages but all works with single Relational ID but not working with Array of relational ID's, let assume I have two Collection Companies and Meteor.users below is my Company Document Looks like
{
_id : "dYo4tqpZms9j8aG4C"
owner : "yjzakAgYWejmJcuHz"
name : "Labbaik Waters"
peoples : ["yjzakAgYWejmJcuHz", "yjzakAgYWejmJcuHz"],
createdAt: "2019-09-18T15:33:29.952+00:00"
}
here you can see peoples field contains the user ID's as Array, so How I publish this userId's as user Documents, as for example I tried the most popular meteor package named publishComposit, when I tried Loop in Children's find, I got undefined in children i.e below
publishComposite('compoundCompanies', {
find() {
// Find top ten highest scoring posts
return Companies.find({
owner: this.userId
}, {sort: {}});
},
children: [
{
find(company) {
let cursors = company.peoples.forEach(peopleId => {
console.log(peopleId)
return Meteor.users.find(
{ _id: peopleId },
{ fields: { profile: 1 } });
})
//here cursor undefined
console.log(cursors)
return cursors
}
}
]
});
and if I implement async loop in children's find I got error like below code
publishComposite('compoundCompanies', {
find() {
// Find top ten highest scoring posts
return Companies.find({
owner: this.userId
}, {sort: {}});
},
children: [
{
async find(company) {
let cursors = await company.peoples.forEach(peopleId => {
console.log(peopleId)
return Meteor.users.find(
{ _id: peopleId },
{ fields: { profile: 1 } });
})
//here cursor undefined
console.log(cursors)
return cursors
}
}
]
});
the error occured in above code is Exception in callback of async function: TypeError: this.cursor._getCollectionName is not a function
I don't know what I am exactly doing wrong here, or implementing package function not as intended any help will be greatly appropriated
EDIT: my desired result should be full user documents instead of ID no matter it mapped in same peoples array or as another fields I just want as below
{
_id: "dYo4tqpZms9j8aG4C",
owner: "yjzakAgYWejmJcuHz",
name: "Labbaik Waters",
peoples: [
{
profile: {firstName: "Abdul", lastName: "Hameed"},
_id: "yjzakAgYWejmJcuHz"
}
],
createdAt: "2019-09-18T15:33:29.952+00:00"
}
I ran into a similar problem couple of days ago. There are two problems with the provided code. First, using async; it's not needed and rather complicates things. Second, publishComposite relies on receiving one cursor not multiple within its children to work properly.
Below is a snippet of the code used to solve the problem I had, hopefully you can replicate it.
Meteor.publishComposite("table.conversations", function(table, ids, fields) {
if (!this.userId) {
return this.ready();
}
check(table, String);
check(ids, Array);
check(fields, Match.Optional(Object));
return {
find() {
return Conversation.find(
{
_id: {
$in: ids
}
},
{ fields }
);
},
children: [
{
find(conversation) {
// constructing one big cursor that entails all of the documents in one single go
// as publish composite cannot work with multiple cursors at once
return User.find(
{ _id: { $in: conversation.participants } },
{ fields: { profile: 1, roles: 1, emails: 1 } }
);
}
}
]
};
});
I'm trying to get data by dates and render html in firestore,
But I cannot that.
The order is wrong.
Because, The data is sorted by user.
How do I get data by date?
{
comments: {
comment_1: {
userRef: users/user_1,
body: "body",
createdAt: Timestamp,
},
comment_2: {
userRef: users/user_2,
body: "body",
createdAt: Timestamp,
},
comment_3: {
userRef: users/user_1,
body: "body",
createdAt: Timestamp,
},
},
users: {
user_1 : {
name: "test"
},
user_2 : {
name: "test"
},
}
}
db.collection('comments').orderBy('createdAt').get().then(snapshot=> {
const comment = snapshot.data();
comment.userRef.get().then(userSnapshot => {
const userData = userSnapshot.data();
const comment = document.getElementById('js-comment');
const element = document.createElement('div')
element.innerHTML = `<p>${comment.body}</p>`
comment.appendChild(element);
});
});
You need to sort the data using orderBy the date field as you have in the code, however, you do not have a field createdAt in the data model you have provided.
If your comments data looks like this
comments: {
comment_1: {
userRef: users/user_1,
body: "body",
createdAt: Timestamp
},
comment_2: {
userRef: users/user_2,
body: "body",
createdAt: Timestamp
},
comment_3: {
userRef: users/user_1,
body: "body",
createdAt: Timestamp
},
The timestamp field can be filled in by getting firestore to fill the field using Timestamp.now() when the data is created.
Once the data is read using the orderBy for the comments. You need to specify ASCENDING if you want the data to be increasing order, then the users can be looked up and the data should appear in the correct order.
This is discussed more here https://firebase.google.com/docs/firestore/query-data/order-limit-data