Search code examples
angularngrx

Angular 14 NGRX store leak


I'm still learning NGRX. My app as far as it goes seems to work, however, I have memory leak.

I have an action:

export const basketDetail = createAction('[Basket Component] Basket', props<{ basket: Photo[] }>());

I have a reducer:

export const initialBasketState: ReadonlyArray<Photo> = [];

export const basketReducer = createReducer(
  initialBasketState,
  on(basketDetail, (state, { basket }) => {
      return basket;
  })
);

then I use these in the following way:

constructor(private store: Store, private basketStore: Store<{ basket: Photo[] }>) { }
     
  basketPhoto: Array<Photo> = [];
          
  AddToBasket(photo: Photo) {
    this.basketPhoto = [...this.basketPhoto, photo];
    this.basketStore.dispatch(basketDetail({ basket: this.basketPhoto }))
  }

When I look in redux devtools. The first time I add to the basket I see a basket object with the added item (all good), but when I add a second item. I see 2 basket objects, 1 with the original and the other with 2. What I really want to see is just 1 basket object that is updated with any additional items not a new basket object each time I add an item.

any help on the correct way of doing this would be great.

***** Update ******

If I change my action to be :

export const basketDetail = createAction('[Basket Component] Basket', props<{ basket: Photo }>());

and change my reducer to be:

export const initialBasketState: Array<Photo> = [];

export const basketReducer = createReducer(
  initialBasketState,
  on(basketDetail, (state, { basket }) => ({ ...state, Photo : state.push(basket)})),
);

then change my call to update the basket to be:

export class HomeMobileComponent implements OnInit {
  constructor(private store: Store, private basketStore: Store<{ basket: Photo[] }>) { }

  photos$ = this.store.pipe(select(selectPhotos));
  
  ngOnInit(): void {
    this.store.dispatch(invokePhotosAPI());
  }

  AddToBasket(photo: Photo) {
    this.basketStore.dispatch(basketDetail({ basket: photo }))
  }

I still receive 2 basket objects but now the first basket has the first item and the 2nd basket has the 2nd item. Even though I receive an error:

Cannot add property 0, object is not extensible at Array.push at basket.reducer.


Solution

  • It's hard to know what the expected outcome needs to be without information how the state should look like. But it seems to me that the reducer is an array that holds photos.

    To update the state, use the spread operator to add a photo to the collection.

    on(basketDetail, (state, { basket }) => {
      return [...state, basket]
    }),
    

    Or, if the array is a child property of the state you'll need to first clone the current state and update the basket, also using the spread operator:

    on(basketDetail, (state, { basket }) => {
      return {...state, basket: [...state.basket, basket]}
    }),
    

    To make updating state easier, you can also take a look at ngrx-immer. With it you can just update the state without having to worry about immutability (cloning parts of the state).

    immerOn(basketDetail, (state, { basket }) => {
      state.push(basket)
    }),
    

    Or

    immerOn(basketDetail, (state, { basket }) => {
      state.basket.push(basket)
    }),