Skip to content

get or onValue with onlyOnce: true after a update operation to a path with listener attached returns the data out of date #8810

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
Dric0 opened this issue Feb 25, 2025 · 9 comments

Comments

@Dric0
Copy link

Dric0 commented Feb 25, 2025

Operating System

Ubuntu 20.04.6 LTS

Environment (if applicable)

Chrome Version 133.0.6943.126

Firebase SDK Version

11.3.1

Firebase SDK Product(s)

Database

Project Tooling

React app with Webpack

Detailed Problem Description

I have a listener attached to a specific path, which works as expected. At some point in my application, I need to read from a specific node that it is under the path with the listener attached, right after I updated this node. The data returned is not up to date, being returned the values as the write operation never happened. If I set some delay, like 3 seconds for example, sometimes the returned data from get() works as expected.

Steps and code to reproduce issue

Create a listener to path:

const transactionRef = ref(db, 'financial/transactions/costCenter');
onValue(transactionRef, (snapshot) => {
  const data = snapshot.val();
});

Later, perform a update to a node under the path being listened to:

const updates = {};
updates[`financial/transactions/costCenter/${id}/value`] = someValue;
updates[`financial/transactions/costCenter/${id}/date`] = date;

return update(ref(db), updates);

And then, get the value from the precious path with its updated values:

const dbRef = ref(getDatabase());
get(child(dbRef, `financial/transactions/costCenter/${id}`)).then((snapshot) => {
  if (snapshot.exists()) {
    console.log(snapshot.val());
  }
}).catch((error) => {
  console.error(error);
});

Or using onValue:

return onValue(ref(db, `financial/transactions/costCenter/${id}`), (snapshot) => {
  // ...
}, {
  onlyOnce: true
});

The value returned from the read operation is out of date, making it looks like the write operation never happened, however the values are successfully written to the database

@Dric0 Dric0 added new A new issue that hasn't be categoirzed as question, bug or feature request question labels Feb 25, 2025
@google-oss-bot
Copy link
Contributor

I couldn't figure out how to label this issue, so I've labeled it for a human to triage. Hang tight.

@rizafran rizafran added api: database and removed needs-triage new A new issue that hasn't be categoirzed as question, bug or feature request labels Feb 26, 2025
@DellaBitta
Copy link
Contributor

Hi @Dric0,

I wasn't able to reproduce this given the code snippets you're provided. Is there a chance that you can create a minimal reproducible test case that exhibits this behavior? Thanks!

@Dric0
Copy link
Author

Dric0 commented Feb 27, 2025

Hi @DellaBitta,
I created a repo to reproduce the issue aaaand.... It's not happening, so I checked the very same code in my application from yesterday and (for my surprise) it is working. Yesterday, when I had the listener removed, the get operation was working as expected, with the listener the get was returning the data out of date. I don't know if it was some cache related issue, but I am very sorry for the inconvenience, I even created a cloud function to perform the get operation and get the correct values because of this

@DellaBitta
Copy link
Contributor

Hi @Dric0,

Great news! I'll leave this open and it will auto close in 2 weeks. In the meantime if you encounter the problem again then let us know here and it will keep the ticket open. Thanks!

@Dric0
Copy link
Author

Dric0 commented Feb 28, 2025

Hello @DellaBitta,
I believe the bug is related to the startAt and endAt of the listener attached, probably the reason why it worked yestarday (the data being tested was different). It seems that if we have a listener such as:

const db = getDatabase();
  const transactionRef = ref(db, 'financial/transactions/costCenter');
  const queryRef = query(
    transactionRef,
    orderByChild('date'),
    startAt('2025-03-01'),
    endAt('2025-03-08\uf8ff'),
  );
  return onValue(queryRef, (snapshot) => {
    const data = snapshot.val();
    console.log(data);
  });

And we perform write/read operations on a field under financial/transactions/costCenter but outside of startAt/endAt params, for example a node with date field equals '2025-02-28', the bug is reproducible.
I created a repo using firebase quickstarter-js. In the first button of the screen we can add the initial data and the second button writes and reads the new changed data. In the first write the new data is printed correctly, but on the second write, which removes the newly added fields, the data printed is the same showed after the first write, even the data being removed on the database

@jbalidiong
Copy link
Contributor

@Dric0, thanks for providing the MCVE, and apologies for the delayed response. I was able to replicate the behavior using your example. After modifying your code, it now works as intended. Here’s what I changed:

  const queryRef = query(
    transactionRef,
    orderByChild('date'),
    startAt('2025-01-01'),
    endAt('2025-03-08\uf8ff'),
  );

From what I observed in the original code, the initial listener returns no nodes. As a result, you wouldn’t be able to listen for any updates since there is no nodes to listen to. However, with the updated code, every update is successfully logged using onValue().

To address this, could you modify the listener to return the initial snapshot that includes the initial data e.g. lower the value of the startAt condition to include the data or make the initial value to be in between the startAt and endAt condition? Let me know if you're still experiencing any issues, and please share the logs, code changes, and steps to reproduce the problem.

@Dric0
Copy link
Author

Dric0 commented Mar 22, 2025

Hey @jbalidiong, thanks for replying

The problem is when a update operation was made to a old node outside the listener startAt() and endAt() params while the listener is active. Let's say the user is reviewing some old operation he/she performed and when I try to fetch the most recent version of this node with get(), it fails, instead showing the data before the update was made.

I could set the listener param to include this data, however it would not be ideal because it would fetch a lot of old data and this would be a very specific scenario when the user is changing a old node, which sometime happens. My current workaround is to fetch this old node from a cloud function to make sure it is its most up to date value. Thanks!

@hsubox76
Copy link
Contributor

I'm still not sure I'm following what we're expecting to see. I tried to simplify your example because there's a lot going on in there. This is what I got as a minimal repro - this is a single file with nothing else needed. I tried to copy the logic you have in the firebase quickstart sample repo, let me know if I'm missing anything. I do notice that in that repro, you initially populated fields named "value" and "date" and then updated fields named "paymentValue" and "paymentDate" which I'm not sure if it was intentional or not. I changed them to be the same name both times. I put in the comments the console log strings that I got, which I think are what's expected. Let me know if you're seeing something different or if I haven't called all the correct operations in the right order. From what you are saying I would expect the "get" operation to return something incorrect (value: 2), but I'm not seeing that

I also tried a variation where I added a third entry ("id3") with a date inside the range. This is logged in the onValue call instead of being null but it doesn't change anything else.

I also tried putting a 1s delay between various steps and it didn't result in anything.

import { initializeApp } from "firebase/app";
import {
  getDatabase,
  ref,
  onValue,
  query,
  orderByChild,
  startAt,
  endAt,
  update,
  get,
  child
} from "firebase/database";

//your firebase config
const firebaseConfig = {
  /** my config **/
};

const app = initializeApp(firebaseConfig);
const db = getDatabase(app);

const transactionRef = ref(db, "financial/transactions/costCenter");
const queryRef = query(
  transactionRef,
  orderByChild('date'),
  startAt('2025-03-01'),
  endAt('2025-03-08\uf8ff'),
);

onValue(queryRef, (snapshot) => {
  const data = snapshot.val();
  console.log('onValue', data);
  // logs null once
});

await update(ref(db), {
  'financial/transactions/costCenter/id1': { value: 1, date: '2025-02-28' },
  'financial/transactions/costCenter/id2':  { value: 2, date: '2025-02-28' }
});

await update(ref(db), {
  'financial/transactions/costCenter/id1/value': 10,
  'financial/transactions/costCenter/id2/date':  '2025-02-28'
});

const getSnap = await get(child(ref(db), 'financial/transactions/costCenter/id1'));
console.log('get value', getSnap.val());
// "get value {date: '2025-02-28', value: 10}"

@Dric0
Copy link
Author

Dric0 commented Apr 2, 2025

@hsubox76 Sorry about the messy repo provided
I will make a few changes to your code with comments, so it follows the same order I did to reproduce the issue

import { initializeApp } from "firebase/app";
import {
  getDatabase,
  ref,
  onValue,
  query,
  orderByChild,
  startAt,
  endAt,
  update,
  get,
  child
} from "firebase/database";

//your firebase config
const firebaseConfig = {
  /** my config **/
};

const app = initializeApp(firebaseConfig);
const db = getDatabase(app);

/** initial database values, some inside and some outside the query range **/
await update(ref(db), {
  'financial/transactions/costCenter/id1': { value: 1, date: '2025-02-28' },
  'financial/transactions/costCenter/id2':  { value: 2, date: '2025-02-28' },
  'financial/transactions/costCenter/id3':  { value: 2, date: '2025-03-02' },
  'financial/transactions/costCenter/id4':  { value: 2, date: '2025-03-03' }
});

/** after the database has its initial values, we set up the listener **/
const transactionRef = ref(db, "financial/transactions/costCenter");
const queryRef = query(
  transactionRef,
  orderByChild('date'),
  startAt('2025-03-01'),
  endAt('2025-03-08\uf8ff'),
);

onValue(queryRef, (snapshot) => {
  const data = snapshot.val();
  console.log('onValue', data);
  /** the values shown here are correct and up to date, logging id3 and id4 **/
});

/** while we have the listener up, perform a update operation to a data outside of the listener query range **/
await update(ref(db), {
  'financial/transactions/costCenter/id1/value': 10,
  'financial/transactions/costCenter/id2/date':  '2025-02-28'
});

/** then, we try to fetch the updated data **/
const getSnap = await get(child(ref(db), 'financial/transactions/costCenter/id1'));
console.log('get value', getSnap.val());
// "get value {date: '2025-02-28', value: 1}"
/** the data showed is the "old" one **/

Following this order of events, the get(child(ref(db), 'financial/transactions/costCenter/id1')) returns the initial value, without the recent changes. If there wasn't a listener, the get returns the updated value.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants