I want to apply server side search filter by text using redux toolkit.
I have two query builder methods in place. One for fetching all items and second for fetching only filtered data.
Query builder for fetching all items is
getAllBlogs: builder.query<BlogType[], void>({
queryFn: async () => {
const collectionRef = collection(Firestore, BLOG_COLLECTION)
const q = query(collectionRef, limit(1000))
const resp = await getDocs(q)
return {
data: resp.docs.map((doc) => doc.data() as BlogType),
}
},
providesTags: (result) => {
const tags: { type: 'Blogs'; id: string }[] = [
{ type: 'Blogs', id: 'LIST' },
]
if (result) {
result.forEach(({ id }) => {
tags.push({
type: 'Blogs',
id,
})
})
}
return tags
},
}),
This works fine and I'm getting the whole list through useGetAllBlogsQuery data.
Query builder for fetching filtered data is here: (Partially completed)
getBlogsByTitle: builder.query<BlogType[], string>({
queryFn: async (title) => {
const collectionRef = collection(Firestore, BLOG_COLLECTION)
const q = query(
collectionRef,
where('searchIndex', 'array-contains', title),
limit(1000),
)
const resp = await getDocs(q)
return {
data: resp.docs.map((doc) => doc.data() as BlogType), // Correct data
}
},
// I'm trying to only push the resultant items in state. This is not working
providesTags: (result) => {
const tags: { type: 'Blogs'; id: string }[] = []
if (result) {
result.forEach(({ id }) => {
tags.push({
type: 'Blogs',
id,
})
})
}
return tags
},
}),
I have react component looks like this where I'm calling these queries.
const Blogs: NextPage = () => {
const { data: blogs } = blogsApi.useGetAllBlogsQuery()
const [getBlogsByTitle] = blogsApi.useLazyGetBlogsByTitleQuery()
const debounced = useDebouncedCallback(async (value) => {
const { data } = await getBlogsByTitle(value)
console.log(data) // Correct data
}, 500)
return (
<div>
<InputText
onChange={(e) => debounced(e.target.value)}
/>
</div>
)}
The above code has two functionalities.
Fetch all the items on initial load.
Filter when debounced function is being called.
What I want is when getBlogsByTitle is called it will auto update the same state blogs in redux and we don't have to do much.
We are getting correct response in getBlogsByTitle but this query is not updating state with only its filtered response.
I'm new to redux-toolkit. Can someone help me out here where am I doing wrong ?
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.
For example I have dynamic filter for my list of books where I can set specific color, authors and categories.
This filter can set multiple colors at once and multiple categories.
Book > Red, Blue > Adventure, Detective.
How can I add "where" conditionally?
firebase
.firestore()
.collection("book")
.where("category", "==", )
.where("color", "==", )
.where("author", "==", )
.orderBy("date")
.get()
.then(querySnapshot => {...
As you can see in the API docs, the collection() method returns a CollectionReference. CollectionReference extends Query, and Query objects are immutable. Query.where() and Query.orderBy() return new Query objects that add operations on top of the original Query (which remains unmodified). You will have to write code to remember these new Query objects so you can continue to chain calls with them. So, you can rewrite your code like this:
var query = firebase.firestore().collection("book")
query = query.where(...)
query = query.where(...)
query = query.where(...)
query = query.orderBy(...)
query.get().then(...)
Now you can put in conditionals to figure out which filters you want to apply at each stage. Just reassign query with each newly added filter.
if (some_condition) {
query = query.where(...)
}
Firebase Version 9
The docs do not cover this but here is how to add conditional where clauses to a query
import { collection, query, where } from 'firebase/firestore'
const queryConstraints = []
if (group != null) queryConstraints.push(where('group', '==', group))
if (pro != null) queryConstraints.push(where('pro', '==', pro))
const q = query(collection(db, 'videos'), ...queryConstraints)
The source of this answer is a bit of intuitive guesswork and help from my best friend J-E^S^-U-S
With Firebase Version 9 (Jan, 2022 Update):
You can filter data with multiple where clauses:
import { query, collection, where, getDocs } from "firebase/firestore";
const q = query(
collection(db, "products"),
where("category", "==", "Computer"),
where("types", "array-contains", ['Laptop', 'Lenovo', 'Intel']),
where("price", "<=", 1000),
);
const docsSnap = await getDocs(q);
docsSnap.forEach((doc) => {
console.log(doc.data());
});
In addition to #Doug Stevenson answer. When you have more than one where it is necessary to make it more dynamic as in my case.
function readDocuments(collection, options = {}) {
let {where, orderBy, limit} = options;
let query = firebase.firestore().collection(collection);
if (where) {
if (where[0] instanceof Array) {
// It's an array of array
for (let w of where) {
query = query.where(...w);
}
} else {
query = query.where(...where);
}
}
if (orderBy) {
query = query.orderBy(...orderBy);
}
if (limit) {
query = query.limit(limit);
}
return query
.get()
.then()
.catch()
}
// Usage
// Multiple where
let options = {where: [["category", "==", "someCategory"], ["color", "==", "red"], ["author", "==", "Sam"]], orderBy: ["date", "desc"]};
//OR
// A single where
let options = {where: ["category", "==", "someCategory"]};
let documents = readDocuments("books", options);
Note that a multiple WHERE clause is inherently an AND operation.
If you're using angular fire, you can just use reduce like so:
const students = [studentID, studentID2,...];
this.afs.collection('classes',
(ref: any) => students.reduce(
(r: any, student: any) => r.where(`students.${student}`, '==', true)
, ref)
).valueChanges({ idField: 'id' });
This is an example of multiple tags...
You could easily change this for any non-angular framework.
For OR queries (which can't be done with multiple where clauses), see here.
For example, there's an array look like this
const conditionList = [
{
key: 'anyField',
operator: '==',
value: 'any value',
},
{
key: 'anyField',
operator: '>',
value: 'any value',
},
{
key: 'anyField',
operator: '<',
value: 'any value',
},
{
key: 'anyField',
operator: '==',
value: 'any value',
},
{
key: 'anyField',
operator: '==',
value: 'any value',
},
]
Then you can just put the collection which one you want to set query's conditions into this funcion.
function* multipleWhere(
collection,
conditions = [{ field: '[doc].[field name]', operator: '==', value: '[any value]' }],
) {
const pop = conditions.pop()
if (pop) {
yield* multipleWhere(
collection.where(pop.key, pop.operator, pop.value),
conditions,
)
}
yield collection
}
You will get the collection set query's conditions.
async yourFunction(){
const Ref0 = firebase.firestore().collection("your_collection").doc(doc.id)
const Ref1 = appointmentsRef.where('val1', '==',condition1).get();
const Ref2 = appointmentsRef.where("val2", "!=", condition2).get()
const [snapshot_val1, snapshot_val2] = await Promise.all([Ref1, Ref2]);
const val1_Array = snapshot_val1.docs;
const val2_Array = snapshot_val2.docs;
const globale_val_Array = val1_Array .concat(val2_Array );
return globale_val_Array ;
}
/*Call you function*/
this.checkCurrentAppointment().then(docSnapshot=> {
docSnapshot.forEach(doc=> {
console.log("Your data with multiple code query:", doc.data());
});
});
As CollectionRef does not have query method in firebase web version 9,
I modified #abk's answer.
async getQueryResult(path, options = {}) {
/* Example
options = {
where: [
["isPublic", "==", true],
["isDeleted", "==", false]
],
orderBy: [
["likes"],
["title", "desc"]
],
limit: 30
}
*/
try {
let { where, orderBy, limit } = options;
let collectionRef = collection(<firestore>, path);
let queryConstraints = [];
if (where) {
where = where.map((w) => firestore.where(...w));
queryConstraints = [...queryConstraints, ...where];
}
if (orderBy) {
orderBy = orderBy.map((o) => firestore.orderBy(...o));
queryConstraints = [...queryConstraints, ...orderBy];
}
if (limit) {
limit = firestore.limit(limit);
queryConstraints = [...queryConstraints, limit];
}
const query = firestore.query(collectionRef, ...queryConstraints);
const querySnapshot = await firestore.getDocs(query);
const docList = querySnapshot.docs.map((doc) => {
const data = doc.data();
return {
id: doc.id,
...data,
};
});
return docList;
} catch (error) {
console.log(error);
}
}
Simple function where you can specify the path and an array of filters that you can pass and get you documents, hope it helps.
async function filterDoc(path, filters) {
if (!path) return [];
//define the collection path
let q = db.collection(path);
//check if there are any filters and add them to the query
if (filters.length > 0) {
filters.forEach((filter) => {
q = q.where(filter.field, filter.operator, filter.value);
});
}
//get the documents
const snapshot = await q.get();
//loop through the documents
const data = snapshot.docs.map((doc) => doc.data());
//return the data
return data;
}
//call the function
const data = await filterDoc(
"categories_collection",
[
{
field: "status",
operator: "==",
value: "active",
},
{
field: "parent_id",
operator: "==",
value: "kSKpUc3xnKjtpyx8cMJC",
},
]
);
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);
};