Saving a snapshotChanges document in the NGRX state throws the error: Cannot freeze

Issue

I am creating a pagination system with Firebase and for this I have to pass a document to the "startAfter()" function of the "@angular/fire/compat/firestore" library.

In my application I am using NGRX for state management, and when the effect is triggered and a document is added, the error occurs.

I would say that I am not changing the state and I am destructuring all objects and arrays as a precaution, but I still get the error "ERROR TypeError: Cannot freeze"

Technologies:

  1. Ionic
  2. Angular
  3. Firebase – Firestore
  4. ngrx

Model:

export interface Page<T> {
  items: T[],
  primerDoc: any;
  ultimoDoc: any;
  limit: number;
}

Service Code:

  getPopularGames(page: Page<Game>) {
    return this.firestore.collection<Game[]>(environment.db_tables.games, ref => ref.orderBy('average_rating', 'desc').limit(page.limit)).snapshotChanges();
  }
  getMorePopularGames(page: Page<Game>){
    console.log(page);
    return this.firestore.collection<Game[]>(environment.db_tables.games, ref => ref.orderBy('average_rating', 'desc').limit(page.limit).startAfter(page.ultimoDoc)).snapshotChanges();
  }

NGRX:

  • ACTIONS:


    export const loadPopularGames = createAction(
      '[Games] loadPopularGames',
      props }>()
    );
    export const loadPopularGamesSuccess = createAction(
      '[Games] loadPopularGamesSuccess',
      props }>()
    );
    export const loadPopularGamesFail = createAction(
      '[Games] loadPopularGamesFail',
      props()
    );
    export const loadMorePopularGames = createAction(
      '[Games] loadMorePopularGames',
      props }>()
    );
    export const loadMorePopularGamesSuccess = createAction(
      '[Games] loadMorePopularGamesSuccess',
      props }>()
    );
    export const loadMorePopularGamesFail = createAction(
      '[Games] loadMorePopularGamesFail',
      props()
    );

  • EFFECTS:


      loadPopularGames$ = createEffect(() =>
        this.actions$.pipe(
          ofType(gamesActions.loadPopularGames),
          mergeMap((action) => this.gameService.getPopularGames(action.page)
            .pipe(
              map((snapshotChanges: any) => {
    
                const copy = [...snapshotChanges];
    
                const page: Page = {
                  items: copy.map((item: any) => {
                    return {
                      id: item.payload.doc.id,
                      ...item.payload.doc.data()
                    }
                  }),
                  primerDoc: { ...snapshotChanges[0].payload.doc }, //HERE IS THE ERROR
                  ultimoDoc: { ...snapshotChanges[snapshotChanges.length - 1].payload.doc }, //HERE IS THE ERROR
                  limit: action.page.limit
                }
    
                return gamesActions.loadPopularGamesSuccess({ page: { ...page } })
              }),
              catchError(err => of(gamesActions.loadPopularGamesFail({ error: err })))
            )
          )
        )
      );
      loadMorePopularGames$ = createEffect(() =>
        this.actions$.pipe(
          ofType(gamesActions.loadMorePopularGames),
          mergeMap((action) => this.gameService.getMorePopularGames(action.page)
            .pipe(
              map((snapshotChanges: any) => {
                const page: Page = {
                  items: snapshotChanges.map((item: any) => {
                    return {
                      id: item.payload.doc.id,
                      ...item.payload.doc.data()
                    }
                  }),
                  primerDoc: snapshotChanges[0],
                  ultimoDoc: snapshotChanges[snapshotChanges.length - 1],
                  limit: action.page.limit
                }
                return gamesActions.loadMorePopularGamesSuccess({ page: page })
              }),
              catchError(err => of(gamesActions.loadMorePopularGamesFail({ error: err })))
            )
          )
        )
      );

  • REDUCER:


    on(authActions.loadPopularGamesSuccess, (state, { page }) => (
        {
          ...state,
          popular_games: page
        }
      )),
    on(authActions.loadMorePopularGamesSuccess, (state, { page }) => (
        {
          ...state,
          popular_games: {
            ...page,
            items: [...state.popular_games.items, ...page.items]
        }
    }))

Solution

I don't know what the firebase API returns, but my guess is that primerDoc or ultimoDoc cannot be frozen. It could for example, be a class instance.

Docs: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze

Answered By - timdeschryver

Answer Checked By - Timothy Miller (AngularFixing Admin)

Leave a Reply

Your email address will not be published.