firestore order by descending breaks when using startAfter - reactjs

I have a function here that is being used to fetch all the documents in a collection.
The information returned is paginated and will only return the next documents when the user scrolls down the page.
export function fetchScreamsFromFirestore(limit, lastDocSnapshot = null) {
let screamsRef = db
.collection('screams')
.orderBy('createdAt')
.startAfter(lastDocSnapshot)
.limit(limit);
return screamsRef;
}
When I add the 'desc' argument to this function like so:
export function fetchScreamsFromFirestore(limit, lastDocSnapshot = null) {
let screamsRef = db
.collection('screams')
.orderBy('createdAt', 'desc')
.startAfter(lastDocSnapshot)
.limit(limit);
return screamsRef;
}
It then returns nothing. Any thought on how I can fix this?

I am a little late here but I think this might just be a syntax issue?
Try...
export function fetchScreamsFromFirestore(limit, lastDocSnapshot = null) {
let screamsRef = db
.collection('screams')
.orderBy('createdAt', descending: true)
.startAfter(lastDocSnapshot)
.limit(limit);
return screamsRef;
}

Related

How to deal with indexes of multiple where query in firebase?

In my flutter app. I have to fetch data from firebase using multiple filters.
I've done as below. It worked but the problem is that some filter is null so I need to skip it in the firebase query, therefore, even I have only 2 fields (name, age) I have to create 3 indexes for supporting my query. 1st index is for: name, 2nd index is for: age, 3rd index is for: name and age.
Future<List<Transac>> getTrans(TransFilter filter, Transac? lastTrans) async {
const limit = 10;
var result =
_collection.orderBy(filter.orderBy, descending: true).limit(limit);
if (filter.directionId != null) {
result = result.where(directionIdKey, isEqualTo: filter.directionId);
}
if (filter.flag != null) {
result = result.where(Transac.flagKey, isEqualTo: filter.flag);
}
if (filter.officeId != null) {
result = result.where(officeIdKey, isEqualTo: filter.officeId);
}
if (lastTrans != null) {
result = result.startAfter([lastTrans.createdAt.millisecondsSinceEpoch]);
}
final _result = await result.get().then(
(value) => value.docs.map((e) => Transac.fromSnapshot(e)).toList());
return _result;
}
I have tried something as below because I think I just need to create all indexs at once, but it throws an error because I cannot use isNotEqualTo on the field that not use in the first orderby.
Future<List<Transac>> getTrans(TransFilter filter, Transac? lastTrans) async {
const limit = 10;
var result =
_collection.orderBy(filter.orderBy, descending: true).limit(limit);
if (filter.directionId != null) {
result = result.where(directionIdKey, isEqualTo: filter.directionId);
}else {
result = result.where(directionIdKey, isNotEqualTo: '');
}
if (filter.officeId != null) {
result = result.where(officeIdKey, isEqualTo: filter.officeId);
}else {
result = result.where(officeIdKey, isNotEqualTo: '');
}
if (lastTrans != null) {
result = result.startAfter([lastTrans.createdAt.millisecondsSinceEpoch]);
}
final _result = await result.get().then(
(value) => value.docs.map((e) => Transac.fromSnapshot(e)).toList());
return _result;
}
Any solution or suggestion? Your help will be much appreciated, Thank you
You have 3 parameters that you want to filterBy directionIdKey, Transac.flagKey, officeIdKey. You want to orderBy: filter.orderBy.
I believe these parameters are holding the field names.
We need a field that will be present in all queries. We use your orderBy field.
So create 3 composite indexes.
filter.orderBy and directionIdKey ASC
filter.orderBy and Transac.flagKey ASC
filter.orderBy and officeIdKey ASC
This should handle your queries.

Can anyone explain why my state is getting updated even when i dont set it manually

So I just spent an hour debugging this code and finally got it to work, but I would want to know why this happened in the first place. I have a function that takes a value from my state, operates on it and saves the output in another variable in the state. This is the fuction:
getFolderNames = async () => {
const promises = this.state.rows.map(async item => {
if (item[".tag"] == "folder" && item.name.length > 20) {
item.name = await getFolderName(item.name);
return item;
} else return item;
});
const result = await Promise.all(promises);
this.setState({
rowsToDisplay: result
});
};
when i run this function, it was updating both the rows and rowsToDisplay to the result variable when i was only calling setState on only one of them.
Changing the function as below solves the issue but I would like to know why.
getFolderNames = async () => {
const promises = this.state.rows.map(async item => {
if (item[".tag"] == "folder" && item.name.length > 20) {
let item2 = {
...item
};
item2.name = await getFolderName(item.name);
return item2;
} else return item;
});
const result = await Promise.all(promises);
this.setState({
rowsToDisplay: result
});
};
It's because of how JavaScript handles variables. When you set a variable to an array or object, it doesn't make a new object but rather just references the original array/object.
As such, if you set a variable to equal some object, and then set a property of that variable, the original object will also be updated. Check this snippet for an example.
var foo = {changed: false};
var bar = foo;
bar.changed = true;
console.log("foo", foo.changed)
console.log("bar", bar.changed)
You can read more about the subject here: https://codeburst.io/explaining-value-vs-reference-in-javascript-647a975e12a0
I hope this helps you in the future, since I know I also spent many hours banging my head against exactly the sort of cases you described in your original question.

Filter Firestore with multiple where not working

I have this docs
let wall = firebase.wallCollection;
then I want to filter it with multiple where:
location (location == filterLocation)
price (price < filterMaximumPrice)
This is my filter methods on Vue
filterResult(){
let self = this;
if(self.filterLocation!=""){
wall.where('location','==', self.filterLocation);
console.log("Location flag");
}
if(parseInt(self.filterMaximumPrice)!=0){
dinding.where('price','<', parseInt(self.filterMaximumPrice));
console.log("Price flag");
}
wall.get()
.then(snapshots => {
snapshots.forEach(doc => {
self.listFilteredWall.push(doc.data());
}, this);
})
}
The problem is that 2 where function not working and still give all wall output without filter.
How to fix this?
CollectionReference extends Query. The result of where() is a new Query:
Creates a new query that returns only documents that include the
specified fields and where the values satisfy the constraints
provided.
You need to retain the result Query if each where() and use it for the get(). Something like this:
filterResult(){
let self = this;
let query = wall;
if(self.filterLocation!=""){
query = query.where('location','==', self.filterLocation);
console.log("Location flag");
}
if(parseInt(self.filterMaximumPrice)!=0){
query = query.where('price','<', parseInt(self.filterMaximumPrice));
console.log("Price flag");
}
query.get()
.then(snapshots => {
snapshots.forEach(doc => {
self.listFilteredWall.push(doc.data());
}, this);
})
}

Searching JSON array using another JSON array node js

I'm trying to filter a JSON array using another JSON array criteria that I have using (filter).
Here is my code:
function filterArray(object, criteria){
return object.filter(function(obj){
for(var i=0;i<criteria.length;i++){
let criteriaEle = criteria[i];
return Object.keys(criteriaEle).forEach(function(key){
if(obj[key] == criteriaEle[key]){
return obj;
}
})
}
})
}
For example:
object = [{type:1,company:1,color:0,name:a},{type:2,company:1,color:0,name:b},{type:1,company:3,color:0,name:c},{type:4,company:1,color:0,name:d},{type:1,company:1,color:1,name:e}]
criteria = [{type:1,company:1,color:0},{type:1,company:1,color:1}]
So if I give these two arrays to the function it should return
obj = [{{type:1,company:1,color:0,name:a},{type:1,company:1,color:1,name:e}}]
I'm not sure where am I going wrong in this. Please help.
Update:
Also, I do not want to use obj.type or obj.company or object.color as parameters to search as I want to make my code maintainable and do not want to come and update it later if in future more criteria's are added.
const data = [{type:1,company:1,color:0,name:'a'},{type:2,company:1,color:0,name:'b'},{type:1,company:3,color:0,name:'c'},{type:4,company:1,color:0,name:'d'},{type:1,company:1,color:1,name:'e'}];
const criteria = [{type:1,company:1,color:0},{type:1,company:1,color:1}];
function checkCriteria(obj) {
return criteria.some(criterion => {
for (const key in criterion) {
if (criterion[key] !== obj[key]) {
return false;
}
}
return true;
});
}
const filtered = data.filter(checkCriteria);
console.log('Filtered array: ', filtered);
Here is one solution.
Here are some references
Array.some
Array.filter
Based on the comment, adding another snippet to explain the concept of closures.
const data = [{type:1,company:1,color:0,name:'a'},{type:2,company:1,color:0,name:'b'},{type:1,company:3,color:0,name:'c'},{type:4,company:1,color:0,name:'d'},{type:1,company:1,color:1,name:'e'}];
function createCriteriaValidationFunction(criteria) {
return function checkCriteria(obj) {
return criteria.some(criterion => {
for (const key in criterion) {
if (criterion[key] !== obj[key]) {
return false;
}
}
return true;
});
}
}
const criteria = [{type:1,company:1,color:0},{type:1,company:1,color:1}];
const filtered = data.filter(createCriteriaValidationFunction(criteria));
console.log('Filtered array: ', filtered);
It's the same concept as before, however, criteria was defined in the file. This time, criteria can be defined outside and can be passed in to the function. The trick is to create the checkCriteria function on the fly with criteria passed in and available in the closure. In both cases, criteria variable is available in the scope in which checkCriteria is executed.

Fetch a Backbone.Collection made up of other collections

So I have a few types of data:
Post
Project
Event
And each of those data models have their own collection and a route to view them:
/posts => app.postsCollection
/projects => app.projectsCollection
/events => app.eventsCollection
Now I want to add another route:
/ => app.everythingCollection
How can I create a collection which displays an aggregate of the other three collections, but without fetching all the post project and event data again?
Similarly, calling everythingCollection.fetch() would fill the postsCollection, projectsCollection and eventsCollection so that their data was available when they were rendered independently.
The whole point being never to download the same data twice.
Your app.everythingCollection doesn't have to be a really backbone collection. All it needs is just access and fetch to other collections.
You can inherit the Backbone.Events to gain all the events facilities also.
var fetchedRecords = {posts: 0, projects: 0, events: 0};
var Everything = function () {}
_.extend(Everything.prototype, Backbone.Events, {
fetch: function (option) {
that = this;
this.count = 0;
option.success = function () {that.doneFetch(arguments)};
if (fetchRecords.posts == 0) {
option.fetchedName = "posts";
app.postsCollection.fetch(option);
this.count ++;
}
if (fetchRecords.projects == 0) {
option.fetchedName = "projects";
app.projectsCollection.fetch(option);
this.count ++;
}
if (fetchRecords.events == 0) {
option.fetchedName = "events";
app.eventsCollection.fetch(option);
this.count ++;
}
},
donefetch: function (collection, response, options) {
if (this.count <=0) return;
this.count --;
if (this.count == 0) {
if (options.reset) this.trigger("reset");
}
fetchedRecords[options.fetchedName] ++;
},
posts: function () {return app.postsCollection},
projects: function () {return app.projectsCollection},
events: function () {return app.eventsCollection}
});
app.everythingCollection = new Everything;
everythingView.listenOn app.everythingCollection, "reset", everythingView.render;
app.everythingCollection.fetch({reset: true});
You will need to increment fetchedRecrods count to prevent fetch multiple times.
Something like this. Code is untested. But idea is the same.
var EverythingCollection = Backbone.Model.extend({
customFetch: function (){
var collections = [app.postsCollection, app.projectsCollection, app.eventsCollection],
index = -1,
collection,
that = this;
this.reset(); //clear everything collection.
//this function check collections one by one whether they have data or not. If collection don't have any data, go and fetch it.
function checkCollection() {
if (index >= collections.length) { //at this point all collections have data.
fillEverything();
return;
}
index = index + 1;
collection = collections[index];
if (collection && collection.models.length === 0) { //if no data in collection.
collection.fetch({success: function () {
checkCollection();
}});
} else { //if collection have data already, go to next collection.
checkCollection();
}
}
function fillEverything() {
collections.forEach(function (collection) {
if (collection) {
that.add(collection.models); //refer this http://backbonejs.org/#Collection-add
}
});
}
}
});
use like below.
app.everythingCollection = new EverythingCollection();
app.everythingCollection.customFetch();
for other collections, check models length before fetch data. Something like below.
if (app.postsCollection.models.length === 0) {
app.postsCollection.fetch();
}
Store all necessary collections in an array or object at app startup, attach an event listener to each of them listening for the first reset event and remember the ones you fetched in a second array. If the route where you need all collections is used you can fetch the ones not found in the array for the already fetched collections:
(untested, but it will give you the idea of how i suppose to do it)
var allCollections = [app.postsCollection, app.projectsCollection, app.eventsCollection];
var fetchedCollections = [];
$.each(allCollection, function(i, coll){
coll.once("reset", function(){
fetchedCollections.push(coll);
})
});
var fetchAll = function(){
$.each(allCollections, function(i, coll){
if( $.inArray(coll, fetchedCollections ) == -1 ){
coll.fetch();
}
});
}
Do this in your everythingCollection and you have the everythingCollection.fetchAll() functionality you need. You could also override the fetch function of the everythingCollection to first fetch all other collections:
fetch(options){
this.fetchAll();
return Backbone.Collection.prototype.fetch.call(this, options);
}
It sounds like braddunbar's supermodel or benvinegar's backbone.uniquemodel might address your problem
It's also worth checking out Soundcloud's article (see Sharing Models Between Views) on building Soundcloud next. They have a similar approach to the above two plugins in solving this problem.

Resources