Dale Snowdon Dale Snowdon - 2 months ago 14
Javascript Question

Firebase Web SDK - Transaction causing on('child_added', func.. to be called

See jsbin.com/ceyiqi/edit?html,console,output for a verifiable example.

I have a reference listening to a database point



is the teams unique number

Under this entry point is a list of jobs

I have a listener on this point with

.on('child_added', function(data) {

I also have a method that does the following transaction:

ref.transaction(function(post) {

// correct the counter
// console.log(post);
// if toggeling on
// if toggeling off

return post;

When invoking the transaction the
is also invoked again, giving me duplicate jobs.

Is this expected behavior?
Should I simply check to see if the item has been got before and add to the array accordingly?
Or am I doing something wrong?

Thanks in advance for your time


You're hitting an interesting edge case in how the Firebase client handles transactions. Your transaction function is running twice, first on null and then on the actual data. It's apparent to see if you listen for value events on /jobs, since then you'll see:

  1. initial value
  2. null (when transaction starts and runs on null)
  3. initial value again (when transaction runs again on real data)

The null value in step 2 is the client's initial guess for the current value. Since the client doesn't have cached data for /jobs (it ignores the cached data for /jobs/list), it guesses null. It is clearly wrong. But unfortunately has been like this for a long time, so it's unlikely to change in the current major version of the SDK.

And because of the null in step 2, you'll get child_removed events (which you're not handling right now) and then in step 3 you'll get child_added events to re-add them.

If you handled the child_removed events, you're items wouldn't end up duplicated, but they would still disappear / reappear, which probably isn't desirable. Another workaround in the current setup is to explicitly tell the transaction to not run with the local estimate, which you can do by passing in false as the third parameter:

function transact() {
    var path = 'jobs/';
    var ref = firebase.database().ref(path);
    ref.transaction(function(post) {
      return post;
    }, function(error, committed, snapshot) { 
      if (error) {
        console.log('Transaction failed abnormally!', error);
      } else if (!committed) {
        console.log('Transaction aborted.');
      } else {
        console.log('Transaction completed.');
    }, false);

I'm sorry I don't have a better solution. But as I said: it's very unlikely that we'll be able to change this behavior in the current generation of SDKs.