Tips for obtaining the final document in a VueFire query

Struggling to figure this out as I am not a JavaScript expert. 😢

I'm utilizing Firestore as the database and VuexFire to link the data to VueX state, like this.

 getLeads: firestoreAction(async ({
        bindFirestoreRef
    }) => {
        // return the promise returned by `bindFirestoreRef`
        return bindFirestoreRef('leads', db.collection('leads').orderBy('updated.date', 'desc').limit(30))
    }),
The initial 30 results are fetched, and now I want to add an infinite scroll feature that triggers a function when the scroll reaches the bottom to fetch more data and link it to the same state. Pagination in Firestore requires passing a query cursor for the last fetched document as a reference

Referencing Firebase documentation with vanilla JS:

var first = db.collection("cities")
        .orderBy("population")
        .limit(25);

return first.get().then(function (documentSnapshots) {
  // Get the last visible document
  var lastVisible = documentSnapshots.docs[documentSnapshots.docs.length-1];
  console.log("last", lastVisible);

  // Construct a new query starting at this document,
  // get the next 25 cities.
  var next = db.collection("cities")
          .orderBy("population")
          .startAfter(lastVisible)
          .limit(25);
});
As I am using VuexFire to bind the data to the state, I am struggling to find a way to retrieve the snapshot of the last document fetched by VuexFire (like lastVisible in the code above) so that I can pass it to the next query.

Any assistance would be greatly appreciated. 🙏🏽

Answer №1

In this scenario, imagine I possess a dataset of Customer records and am showcasing the initial 5 entries arranged by the most recent update.

Here's the code snippet:

getCustomers: firestoreAction(({ commit, bindFirestoreRef
}) => {
    bindFirestoreRef('Customers', db.collection('customers')
   .orderBy('updated.date', 'desc').limit(5)).then(documents => {
        commit('POPULATE_TESTCUSTOMERS', documents);
        commit('LAST_DOC', documents[documents.length - 1]);
    });

}),

I'm keeping track of both the fetched data and the last document in the state, iterating through and displaying the names as follows:

Nakheel
Emaar Group
Yapi Kredi Inc
Cairo Amman Bank
Arab Jordan Investment Bank LLC

Later on, I make another call using the last document as the query cursor, anticipating the next set of 5 documents to be retrieved from Firebase, like this:

fetchMoreCustomers: firestoreAction(({ state, bindFirestoreRef
}) => {
    bindFirestoreRef('testCustomers', db.collection('customers')
    .orderBy('updated.date', 'desc')
   .startAfter(state.lastDoc).limit(5))
}),

However, instead of receiving new results, I continue to get the same initial 5 entries from Firestore. What could be the issue here? :(

Answer №2

VueFire and VuexFire internally utilize a serializer function to transform each Document obtained from RTDB or Firestore into the data objects that are linked to the final component or Vuex store state.

The default serializer is provided by the function createSnapshot, which belongs to the vuefire-core library:

/**
 * @param {firebase.firestore.DocumentSnapshot} doc
 * @return {DocumentData}
 */
export function createSnapshot (doc) {
  // sets id as a property of the data object
  return Object.defineProperty(doc.data(), 'id', {
    value: doc.id
  })
}

The default behavior only returns doc.data() with the addition of the id, discarding the original doc object. However, when implementing Firestore pagination using query.startAfter(doc), it becomes necessary to retain the original doc object. The good news is that VueFire and VuexFire offer the flexibility to replace the default serializer with a customized one that preserves the entire doc object like this:

const serialize = (doc: firestore.DocumentSnapshot) => {
  const data = doc.data();
  Object.defineProperty(data, 'id', { value: doc.id });
  Object.defineProperty(data, '_doc', { value: doc });
  return data;
}

We have the option to configure our custom VuexFire serializer either globally through plugin options or on a per-binding basis via binding options.

// Global setup Vue.use(firestorePlugin, { serialize });

// Or for individual binding bindFirebaseRef('todos', db.ref('todos'), { serialize } )

Now with VuexFire, we can access the initial document as state.todos[0]._doc or the last document as

state.todos[state.todos.length-1]._doc
to facilitate pagination queries for collections or "get next" & "get previous" queries that involve single documents (especially relevant when dealing with multi-field sorting in your base query).

Please take note: Since _doc and id are non-enumerable properties, they will not be visible on component or store objects within Vue DevTools.

Answer №3

According to the information provided in the VueFire documentation regarding data binding and its usage, when using the $bind method (which I assume is similar to your bindFirestoreRef), a promise is returned along with the result (while also binding it to this). As stated in the documentation:

this.$bind('documents', documents.where('creator', '==', this.id)).then(documents => {
  // The 'documents' variable will refer to the same property defined in data:
  // this.documents === documents
})

Hence, you should be able to follow the same approach and retrieve the document from the array by using something like this:

bindFirestoreRef('leads', db.collection('leads').orderBy('updated.date', 'desc').limit(30)).then(documents => {
  this.lastDoc = documents[documents.length - 1];
})

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the best way to generate unique mousedown callbacks on the fly?

My goal is to create multiple divs, each with a unique mousedown callback function. However, I want each callback function to behave differently based on the specific div that is clicked. Below is the code I have been using to create the divs and set the ...

What methods does Enzyme have for determining the visibility of components?

I am facing an issue with a Checkbox component in my project. I have implemented a simple functionality to hide the checkbox by setting its opacity : 0 based on certain conditions within the containing component (MyCheckbox) MyCheckBox.js import React fr ...

Why is it that methods lose their binding when they are returned from a ternary operator?

class TestClass { constructor() { this.prop = 5; } MethA() { console.log(this); console.log(this.prop); } MethB() { (true ? this.MethA : null)(); } } Test = new TestClass(); Test.MethB(); What is the ...

Utilize the npm module directly in your codebase

I am seeking guidance on how to import the source code from vue-form-generator in order to make some modifications. As a newcomer to Node and Javascript, I am feeling quite lost. Can someone assist me with the necessary steps? Since my Vue project utilize ...

Combining objects in JavaScript

I am currently working on converting the object received from the server into a format compatible with the backend system. I have a received object that looks like this { 'User.permissions.user.view.dashboard': true, 'Admin.permissio ...

Having a problem with the glitch effect in Javascript - the text is oversized. Any tips on how to resize

I found some interesting code on the internet that creates a glitch text effect. However, when I implement it, the text appears too large for my webpage. I'm struggling to adjust the size. Here is the current display of the text: too big This is how ...

Using JavaScript to fetch elements by their ID along with a button

UPDATE: I have corrected the semi-colons, case sensitivity, and brackets in the code. It functions properly if I eliminate the functions after buttonPARTICULAR! Why is that? UPDATE: Issue resolved. My mistake. Apologies!!! :-Z When I simplify it like thi ...

Disrupting a Program Operation

We are utilizing the gauge Google Chart applet to visually track the failure rates of message transfers on a SOAP interface via AJAX. My goal is to make the page's background flash red and white when the failure rate reaches 50% or higher, and remain ...

Ways to retrieve class attributes in a child context

When trying to access the class properties (or methods) from another scope, I find myself having to store it in a local variable within the function scope. class MyClass { constructor(API) { this.API = API; this.property = 'value& ...

Having trouble encoding PHP array in jQuery

I am struggling to find the index in a JSON array. The browser is displaying undefined data. I have posted the code snippets below. Here is my PHP encoded array: [{"voo_Cod":"1","voo_CidadeOrigem":"1","voo_CidadeDestino":"2","voo_Data":"2015-07-13 07:00: ...

Utilizing Laravel to Retrieve Information from an API

I am currently working with a database table that requires continuous updating. game_id | end_time My challenge now is figuring out how to create a job that will run based on the end_time. This job will need to retrieve data from a third-party API, and i ...

Vue ChartJS - Doughnut Chart with customized label in the center

I am interested in embedding text specifically within a Doughnut chart. Within my vuejs project, I am utilizing the following plugin: https://github.com/apertureless/vue-chartjs Presently, the text is displaying for all types of charts. My intention is ...

I am looking to show images based on the number chosen from the dropdown menu in CodeIgniter

When a number is selected from the dropdown menu, I want to display images accordingly. The options in the dropdown are 9, 12, and 18. Here is the code snippet for my view page: <form action="<?php echo base_url();?>roxcontrol/numberdisplay" id=" ...

bind a class property dynamically in real-time

I need to dynamically generate a TypeScript class and then add a property to it on the go. The property could be of any type like a function, Promise, etc., and should use this with the intention that it refers to the class itself. let MyClass = class{ ...

Error message occurs when creating a pie chart with invalid values for the <path> element in Plottable/D3.js

For those who need the code snippets, you can find them for download here: index.html <!doctype html> <html> <head> <meta charset="UTF-8"> <!-- CSS placement for legend and fold change --> </head> <body ...

In search of a way to implement a conditional class binding within the current project

I am working on enhancing a child component within an existing Vue project. My goal is to allow users to add custom classes to the child component. I typically use :class="customClasses" in the dom-element for this purpose. However, there is alre ...

Even with manual installation, the npm package still encounters dependency errors

Having trouble implementing the Imgur package from NPM into my Angular web app. The installation and import seemed to go smoothly, but when initializing a variable with the package, I encounter compile errors pointing to missing dependencies like 'cry ...

Exploring AngularJS: Understanding the Differences Between $http's Success and Then

Can someone explain the difference between these methods for me? I am curious about the distinctions between .then and .success functions, as well as .error. Thank you. // Simple GET request example: $http({ method: 'GET', url: '/some ...

Verification of email address is required, emails must be unique and not duplicated

I am working on validating email addresses to ensure they are not repeated. So far, I have successfully pushed them from the server into an array using regex for validation. What steps should I take next to compare and further validate these emails? ...

Error message consistently pops up when using the jQuery search feature

My jQuery function is fetching data from a different domain, and I want to use AJAX to display that data when the user enters a value into the search box. However, no matter if I search by .productName or .itemCode, the app always gives me an error messa ...