r-park / todo-react-redux

Todo app with Create-React-App • React-Redux • Firebase • OAuth
https://todo-react-redux.firebaseapp.com
MIT License
1.02k stars 272 forks source link

Quick question about Firebase-list.js #64

Closed jakelowen closed 7 years ago

jakelowen commented 7 years ago

Hope you don't mind me asking via this means, but I have a very quick question about the firebase-list.js class used in https://github.com/r-park/todo-react-redux/blob/master/src/core/firebase/firebase-list.js

I've spent hours studying this code and inserting lots of console logs to track the flow / sequence of events.. I can not wrap my mind around how ref.once function one line 52 is working.

How do you get it to wait until the all the ref.on('child_added' events occur before the .once function emits the list?

I don't see any explicit sequencing here, so I can not figure it out. It appears that events happen in this order:

  1. Action starts the subscription.
  2. list is initialized as empty array.
  3. the .on('value') function fires.. and loads all the items from the ref into the list.
  4. the .once function then sets initialized to true and emits the list.

How in the world is the .once function waiting until all the .on('value') process is complete? And do I read this correctly that the .once() is not actually utilizing the data it grabs because there is no snapshot being passed to the callback / no unwrapping?

Can anyone help me understand?

r-park commented 7 years ago

Hi @jakelowen

Firebase takes care of it for us :)

From the docs:

The value event is used to read a static snapshot of the contents at a given database path, as they existed at the time of the read event. It is triggered once with the initial data and again every time the data changes.

The implementation in firebase-list.js is just an example to show how you can use the value callback to wait until the initial data has loaded.

Depending on your needs, you might choose not to use value in this way, and instead rely solely on child_added (or vice versa).

jakelowen commented 7 years ago

Thanks @r-park - Thank you, I think I'm with you.

In this code snippet (directly from firebase-list.js)

ref.once('value', () => {
      initialized = true;
      emit(this._actions.onLoad(list));
    });

    ref.on('child_added', snapshot => {
      if (initialized) {
        emit(this._actions.onAdd(this.unwrapSnapshot(snapshot)));
      }
      else {
        list.push(this.unwrapSnapshot(snapshot));
      }
    });

Are both ref.once('value'.... AND ref.on('child_added'.. both separate database reads that are happening simultaneously? So if there are 3 children at that path, I am actually downloading the 3 children twice (total of 6 records), but because .once is not reading the snapshot it is essentially throwing away the data collected and thus only being used as a timer mechanism to emit the list (presumably after the .on('child_added') is also done populating the list?

Could I theoretically avoid the ref.once('value'.... call altogether by expanding the ref.on('child_added'.. section to something like: (pseudocode, untested):

     ref.on('child_added', snapshot => {
        if (initialized) {
          emit(this._actions.onAdd(this.unwrapSnapshot(snapshot)))
        } else {
          initialized = true;
          emit(this._actions.onLoad(this.unwrapSnapshot(snapshot)))
        }
      })

Or is the ref.once('value'.... playing some special role that I am failing to grasp?

jakelowen commented 7 years ago

Oh, I see. My above rewritten code only grabs the first item from child_added into onLoad. All subsequent children on initial read are put into onAdd. So I need a way to wait for all children before I dispatch to onLoad. That must be what your .once function was doing.

r-park commented 7 years ago

If you're only going to use child_added, the initialized flag is probably unnecessary, unless you want to know when the first item has been loaded.

You can simplify it to something like this:

//let initialized = false;
//let list = [];

/*
ref.once('value', () => {
  initialized = true;
  emit(this._actions.onLoad(list));
});
*/

ref.on('child_added', snapshot => {
  emit(this._actions.onAdd(this.unwrapSnapshot(snapshot)));
}

Keep in mind that this._actions.onAdd() will trigger a view update each time it's fired.

r-park commented 7 years ago

That must be what your .once function was doing.

👍

jakelowen commented 7 years ago

Thank you @r-park - I follow now. Kudos on an extremely well written code example. I don't think I could have cracked firebase with redux had it not been for you.

For future reference to other future searchers.., this SO question addresses why this approach of both a .once and a .on('child_added') work well together: http://stackoverflow.com/questions/27978078/how-to-separate-initial-data-load-from-incremental-children-with-firebase?noredirect=1&lq=1