0

I have built a pretty complex list(s) in a component that all query the same way. When I hit the end of my list, I increase my fetch limit by 10... Everything was going great until I realized I am loading all the data over again (not just the new 10). I want to avoid all these reads to reduce costs - I am not sure if firebase ignores the data that is already loaded or not... My list is running quite smooth when I fetch the extra 10 every time but I don't know if behind the scenes things will come back to haunt me!

UPDATE

I have updated my code based off some helpful feedback and I believe I have things working how they should be. There is only one thing that I am concerned about. When I reach the bottom of my list after my first fetch I am passing all of the previous data to my action creator then concatinating it to the new fetched data. This repeats everytime I hit the bottom as the list grows. My list is going to have over 1000 records so I am worried about potential performance issues, should I be? Have a look at my new attempt below!

Original Attempt:

onEndReached = () => {
 const { searchFilterText, effectVal } = this.state;

   this.setState({
     strainFetchIndex: this.state.strainFetchIndex + 10
   }, () => {
     const offset = this.state.strainFetchIndex;

      if (searchFilterText === '') {
       this.props.strainsFetch(offset);
     } else if (searchFilterText === 'Hybrid' || searchFilterText === 'Salt' || searchFilterText === 'Initial') {
       this.props.filterStrainAction(searchFilterText, offset);
     } else if (searchFilterText === 'Effects') {
       this.props.filterByEffect(effectVal, offset);
     }
  });
}

//HERES 1 of 4 ACTION CREATORS WHERE I FETCH MORE DATA (ALL ARE SIMILAR)

    export const strainsFetch = (offset) => {
      const ting = offset || 1;
        return (dispatch) => {
          firebase.database().ref('/strains')
            .orderByKey()
            .limitToFirst(1 * ting)
            .on('value', snapshot => {
              dispatch({ type: STRAINS_FETCH_SUCCESS, payload: snapshot.val() });
            });
        };
      };

New attempt:

  onEndReached = () => {
    const { searchFilterText } = this.state;
    const { lastKey } = this.props;
    const currentStrains = this.props.strains;

      if (this.state.filterLabel === 'Favourites') {
        return null;
      }
      if (searchFilterText === '') {
        //here I pass all previous records along with the last key (last key comes from my action creator)
        this.props.strainsFetch(currentStrains, lastKey);
      } 
    }



    //ACTION CREATOR



    export const strainsFetch = (currentStrains, lastKey) => {
      if (!lastKey) {
        return (dispatch) => {
          // console.log('first Fetch');
          firebase.database().ref('/strains')
            .orderByKey()
            .limitToFirst(10)
            .on('value', snapshot => {
              const snap = snapshot.val();
              const snapKeys = Object.keys(snap);
              const createLastKey = snapKeys[9];

              dispatch({ type: STRAINS_FETCH_SUCCESS, payload: snapshot.val(), key: createLastKey });
            });
        };
      }
        return (dispatch) => {
          // console.log('subsequent Fetch');
          firebase.database().ref('/strains')
            .orderByKey()
            .startAt(`${lastKey}`)
            .limitToFirst(11)
            .on('value', snapshot => {
              const snap = snapshot.val();
              const snapKeys = Object.keys(snap)
              .slice(1);

              const results = snapKeys
                 .map((key) => snapshot.val()[key]);

              const createLastKey = snapKeys[snapKeys.length - 1];
              const concatStrains = _.concat(currentStrains, results);

              dispatch({ type: STRAINS_FETCH_SUCCESS, payload: concatStrains, key: createLastKey });
            });
        };
      };



      //HERE IS WHAT MY REDUCER LOOKS LIKE



    import {
      STRAINS_FETCH_SUCCESS
    } from '../actions/types';

    const INITIAL_STATE = {};

    export default (state = INITIAL_STATE, action) => {
      switch (action.type) {
        case STRAINS_FETCH_SUCCESS:
        // console.log(action.payload);
          return { strains: action.payload, lastKey: action.key };

        default:
          return state;
      }
    };

Is this a bad practice to repeatedly pass the previous data to my action creator over and over as the list continues to grow? Or is there a more efficient way to accomplish this?

Thanks everyone!

Cheers.

hugger
  • 426
  • 4
  • 19
  • You need to provide an offset. Your first call will get items 0-10 during the fetch - a subsequent request would pass up `?offset=10` which starts from the tenth document. – lux Feb 01 '19 at 19:22
  • Hi lux, I'm afraid I don't understand what you are trying to say. I am passing in an offset every time my list reaches the end. It increments the offset by 10 every time which loads 10 more items than before. @lux – hugger Feb 01 '19 at 20:16
  • 1
    endless scroll with Firebase is dramatic, because one needs to attach in reverse order. for example: https://apps-script-community-ar-a9405.firebaseapp.com/ (notice the numbers top right). if I remember that correctly, on subsequent requests one has to fetch 10+1, but skip 1. – Martin Zeitler Feb 01 '19 at 21:55
  • Thanks martin, you're right... It seems to be a huge hassle to get this working properly... How would you recommend I get the last key to be my starting point for the next fetch? – hugger Feb 01 '19 at 23:00

1 Answers1

0

You're correct in that you are re-loading all of the old ones in addition to the new ones. If you only want to load the new ones, then you should use the .startAt() method, check out the relevant docs here.

Your final setup would look something like:

export const strainsFetch = (offset) => {
    const startingIndex = offset || 1;
    const numRecordsToLoad = 10;
    return (dispatch) => {
        firebase.database().ref('/strains')
            .orderByKey()
            .startAt(startingIndex)
            .limitToFirst(numRecordsToLoad)
            .on('value', snapshot => {
                dispatch({ type: STRAINS_FETCH_SUCCESS, payload: snapshot.val() });
            });
    };
};
JeremyW
  • 5,157
  • 6
  • 29
  • 30
  • Hi Jeremy, thank you for confirming that. I am using the realtime database though, not cloud firestore which is where the link leads me. Can I still accomplish this? – hugger Feb 01 '19 at 21:55
  • @hugger Oops! Sorry about that, [here is the link for RTD](https://firebase.google.com/docs/database/web/lists-of-data#filtering_data) docs for `.startAt()`... should be the same thing though. – JeremyW Feb 01 '19 at 21:56
  • Thank Jeremy! the start at would be the key of the last item, correct? if so what how would you recommend I get this value? I've been doing some research and there seems to be no clear solution to achieve this without SO MUCH CODE!! I wouldn't mind if it was for only one query but I have many... It seems pretty crazy to me how there is not a more efficient way to achieve paging like this. – hugger Feb 01 '19 at 22:58
  • @hugger Yep, startAt would be the key of the last item. The key is included in the snapshot you pull down, and the answer to [this question](https://stackoverflow.com/questions/43615466/how-to-get-the-key-from-a-firebase-data-snapshot) shows how for both RTD and Firestore - you'd just need to include the key in the action's payload instead of just `snapshot.val()` alone, and have your reducer handle getting that into the store with the rest of that value's information. Depending on how you have your store structured, that might involve a little bit of reworking the store structure – JeremyW Feb 02 '19 at 16:26
  • Hey @JeremyW! Thanks so much for the help. I think I have things working correctly without loading all of the data over and over again using the `startAt()`, I am just concerned with one thing... I have updated my question with my new attempt, mind checking it out? – hugger Feb 04 '19 at 18:11