What is the reason for my Firestore listener consistently retrieving data from the server despite having offline persistence enabled?

Incorporating Firebase JavaScript Modular Web Version 9 SDK into my Vue 3 / TypeScript application.

My understanding is that when utilizing real-time listeners with offline persistence in Firestore, the process should proceed as follows:

  1. Upon initialization of the listener, the callback triggers with data fetched from the local cache, followed by an immediate fetch from the server to ensure synchronization between the local cache and server data. If the server data matches the local cache, the callback should only trigger once with the data from the local cache.
  2. Upon any changes in the data, the callback fires with the updated information fetched directly from the server, which in turn updates the local cache accordingly.
  3. If there are no changes in the data, subsequent calls to the listener will initiate callbacks with data retrieved solely from the local cache.

However, despite setting up offline persistence, creating a listener for my Firestore data, and monitoring the data read sources...

I noticed an initial read from the local cache (as expected), followed by an unexpected second read from the server. Subsequent reads also continue to come from the server, rather than the local cache as anticipated.

Throughout this testing phase, no alterations were made to the data. Therefore, I expected all data reads triggered by the callback listener to originate from the local cache, not the server.

Oddly enough, the lone instance where a read did come from the local cache was during the initial setup of the listener, which aligns with expectations.

What might be causing this issue?

P.S. To simulate those "subsequent calls", I navigate to a different page within my SPA and then return to the component's page to prompt the behavior again.

src/composables/database.ts

export const useLoadWebsite = () => {
  const q = query(
    collection(db, 'websites'),
    where('userId', '==', 'NoLTI3rDlrZtzWCbsZpPVtPgzOE3')
  );
  const firestoreWebsite = ref<DocumentData>();
  onSnapshot(q, { includeMetadataChanges: true }, (querySnapshot) => {
    const source = querySnapshot.metadata.fromCache ? 'local cache' : 'server';
    console.log('Data came from ' + source);
    const colArray: DocumentData[] = [];
    querySnapshot.docs.forEach((doc) => {
      colArray.push({ ...doc.data(), id: doc.id });
    });
    firestoreWebsite.value = colArray[0];
  });
  return firestoreWebsite;
};

src/components/websiteUrl.vue

<template>
  <div v-if="website?.url">{{ website.url }}</div>
</template>

<script setup lang="ts">
import { useLoadWebsite } from '../composables/database';
const website = useLoadWebsite();
</script>

Answer №1

Everything is working as intended. The Firestore local persistence feature is not meant to replace the backend entirely. It serves as a temporary data source when the backend is unavailable. When the backend is accessible, the SDK will ensure that the client app stays in sync with it and receives all updates from there.

To make a query use only the cache and not the backend, you can specify the cache as the data source programmatically.

If you wish to stop receiving any updates from the server, you can disable network access completely.

For further reading:

Answer №2

I finally uncovered the reason behind the unexpected result I was receiving.

The culprit turned out to be the { includeMetadataChanges: true } option.

According to the information provided here in the documentation, enabling this option triggers a listener event for metadata changes.

As a result, the listener callback was firing not only on data reads and writes but also on each metadata change, leading to peculiar outcomes.

Upon removing this option, the functionality returned to normal as expected. This was confirmed by comparing it against the Usage graphs in the Firebase console that illustrate the number of reads and snapshot listeners.

Below is the revised code without the problematic option:

export const useLoadWebsite = () => {
  const q = query(
    collection(db, 'websites'),
    where('userId', '==', 'NoLTI3rDlrZtzWCbsZpPVtPgzOE3')
  );
  const firestoreWebsite = ref<DocumentData>();
  onSnapshot(q, (querySnapshot) => {
    const source = querySnapshot.metadata.fromCache ? 'local cache' : 'server';
    console.log('Data came from ' + source);
    const colArray: DocumentData[] = [];
    querySnapshot.docs.forEach((doc) => {
      colArray.push({ ...doc.data(), id: doc.id });
    });
    firestoreWebsite.value = colArray[0];
  });
  return firestoreWebsite;
};

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

Tips on retrieving a strongly typed value from a method using Map<string, object>

Having had experience working with C# for a while, I recently ventured into a Node.js project using TypeScript V3.1.6. It was exciting to discover that TypeScript now supports generics, something I thought I would miss from my C# days. In my C# code, I ha ...

Leveraging .tsx components within nested .tsx components in React Native

Currently, I am delving into the world of building apps using TypeScript in React Native. Coming from a background as a Swift developer, adjusting to JavaScript and TypeScript has been an interesting journey. An observation that stood out to me is the cha ...

What are the steps to globalize the ng-bootstrap datepicker?

For my current project, I am utilizing the ng-bootstrap datePicker component. The demo for my simple datePicker widget can be found here. However, I am now seeking to internationalize it by setting it to use the Russian language. Any assistance with this ...

What are some solutions for resolving a TypeError in the created hook of a Vue.js application

Oops, there seems to be an error: vue.js:597 [Vue warn]: Error in created hook: "TypeError: handlers[i].call is not a function" found in ---> <StageExecs> vue.js <div id="vue-job"> <div class="row"> <h3>test</ ...

React variable should remain consistent and not change unnecessarily

I've been struggling with an issue for about 3 hours now, and I just can't seem to figure it out. Let me walk you through the problem with the code snippet below: import {useEffect} from 'react' function shuffle(tab) { console.table ...

Exploring the capabilities of ECMAScript generators within the Intel XDK Simulator

I am attempting to utilize a generator that has been declared using function* in Intel XDK. The simulate function within XDK is said to be based on Chromium, though it's difficult to determine the specific version ('about' box and similar fe ...

Display PDF file retrieved from the server using javascript

I am currently working on a web application using JavaScript, jQuery, and Node.js. I need to receive a PDF file from the server and display it in a new browser window. While I believe I have successfully received the file on the client side (the window sh ...

Encountering a Problem on Heroku: TypeScript Compilation Error with GraphQL Files - TS2307 Error: Module 'graphql' Not Found or Its Type Declarations Missing

While trying to compile my typescript project with graphql files for deployment on Heroku, I encountered the following error message: node_modules/@types/graphql-upload/index.d.ts(10,35): error TS2307: Cannot find module 'graphql' or its correspo ...

Tips for sending an icon as a prop in React components

I'm struggling to implement an icon as a prop while using props for "optionText" and "optionIcon". The optionText is working fine, but I'm facing issues with the OptionIcon. File where I'm creating props import Icon from ...

What is the best way to initiate WebXR from an iframe in a Next.js environment?

I am currently working on a Next.js app: https://codesandbox.io/s/next-js-forked-6fgnr7?file=/index.tsx I have implemented the functionality for it to open WebXR on Android Chrome when clicking on the AR button located at the bottom left ("in AR betracht ...

Exploring the feature of setting the checked state in Radio.Group using Antd

I am dealing with dynamic data that needs to be displayed in a radio button format. One of the challenges is comparing the dynamically generated id with the active radio id and setting it as checked using Radio.Group. Unfortunately, the current code is no ...

Using AngularJS to manage cookies along with arrays

I am passing my data in this way $cookies.putObject("currentLocation,values,allLocList", obj, vm.tempData, vm.allLocationList); The objects obj and vm.tempData are being sent as objects, while vm.allLocationList is an array that contains a JSON object. W ...

How to retrieve email input using SweetAlert2 in PHP?

Hello there! I'm curious about the most effective method for integrating PHP with Javascript. My goal is to execute some coding tasks once an email address has been entered. swal({ type: "success", title: "Congrats!", text: "Please enter your P ...

Error: The binding element titled implicitly possesses a type of 'any'

Encountering the following issue: ERROR in src/components/Header.tsx:6:18 TS7031: Binding element 'title' implicitly has an 'any' type. 4 | 5 | 6 | const Header = ({title}) => { | ^^^^^ 7 | return( 8 | ...

Exploring the concepts of express post and delete responses that are unclear

It appears that I am facing an issue where trying to access an attribute of an element from a JSON file returns null. Additionally, I am still encountering npm audit problems. What are your thoughts on this situation? Below is the code snippet that has be ...

Error: The server selection process has encountered an unexpected issue

Embarking on my journey with MongoDB and the MERN stack, I turned to these tutorials for guidance: https://medium.com/@beaucarnes/learn-the-mern-stack-by-building-an-exercise-tracker-mern-tutorial-59c13c1237a1 https://www.youtube.com/watch?v=7CqJlxBYj-M ...

angular 2 updating material table

Issue at Hand: I am facing a problem with the interaction between a dropdown menu and a table on my website. Imagine the dropdown menu contains a list of cities, and below it is a table displaying information about those cities. I want to ensure that whe ...

Modify the data type of an object member based on its original type

I am seeking to convert the data type of each member in an object based on the specific member variable. Here is an example: class A { fct = (): string => 'blabla'; } class B { fct = (): number => 1; } class C { fct = (): { o ...

Using Jasmine to simulate an if/else statement in Angular/Typescript unit testing

After making a minor change to an existing function, it has been flagged as new code during our quality checks. This means I need to create a unit test specifically for the new 4 lines of code. The challenge is that there was never a unit test in place for ...

The guide to integrating the npm package 'mysql-import' into a node.js project with TypeScript

I'm currently facing an issue while trying to import a dump to a database using node.js and the npm module 'mysql-import'. Initially, I wrote the code in JavaScript and it worked flawlessly. However, when I attempted to modify it for TypeScr ...