Search code examples
angularngrxngrx-store

Angular NGRX getting error when dispatching to state


My first time using Ngrx, although it is relatively simple, I'm getting an error when dispatching to state. When the user opens the App he needs to enter his name and submit, and the next screen supposed to show the " greeting {{ username }} ". After user submit I'm getting this error --> core.js:6014 ERROR TypeError: object is not iterable (cannot read property Symbol(Symbol.iterator))

Appreciate your help

import { Action } from '@ngrx/store';
import { UserName, WishListItem } from 'src/model/name.model';

export enum AddToStateActionType{
    ADD_NAME = '[ADD_NAME] add name',
    ADD_TO_WISHLIST = '[ADD_TO_WISHLIST] add to wishlist'
}

export class AddWelcomeNameAction implements Action {
    readonly type = AddToStateActionType.ADD_NAME;

    constructor(public payload: UserName){}
}

export class AddToWishList implements Action {
    readonly type = AddToStateActionType.ADD_TO_WISHLIST;

    constructor(public payload: WishListItem){}
}

export type UserAction = AddWelcomeNameAction | AddToWishList ;

<!--Reducer-->

import {AddToStateActionType, UserAction} from './../action/action';
import { UserName, WishListItem } from 'src/model/name.model';

const initialState: any = {
    name: '',
    wishlist: []
};


export function reducer(state: any = initialState, action: any) {
    switch(action.type) {
        case AddToStateActionType.ADD_NAME:
            return [...state, action.payload.name];
        case AddToStateActionType.ADD_TO_WISHLIST:
            return [...state. action.payload.wishlist];
    default:
        return state;
    }
}

<!--AppState-->

import { UserName, WishListItem } from 'src/model/name.model';

export interface AppState {
    readonly username: UserName;
    readonly wishlist: Array<WishListItem> ;
  }

<!--Welcome component-->

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '../app.state/app.state';
import * as Actions from '../../action/action';
import {Router} from '@angular/router';
import { UserName } from 'src/model/name.model';

@Component({
  selector: 'app-welcome',
  templateUrl: './welcome.component.html',
  styleUrls: ['./welcome.component.css']
})
export class WelcomeComponent implements OnInit {
  userName: UserName = {name: ''};

  profileForm = new FormGroup({
    userName: new FormControl(''),
  });
  constructor(private store: Store<AppState>, private router: Router) { }

  ngOnInit() {
  }

  onSubmit() {
   this.store.dispatch(new Actions.AddWelcomeNameAction({name: this.profileForm.value}));
   this.router.navigate(['/search']);
  }

}


Solution

  • The problem is in your reducer for both cases.

    In your code:

    export function reducer(state: any = initialState, action: any) {
        switch(action.type) {
            case AddToStateActionType.ADD_NAME:
                return [...state, action.payload.name];
            case AddToStateActionType.ADD_TO_WISHLIST:
                return [...state. action.payload.wishlist];
        default:
            return state;
        }
    }
    

    you are applying the spread operator incorrectly, resulting in two issues:

    Array Spread Operator: You are applying the array spread operator: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax.

    [...state, action.payload.name]; // This is an array
    

    They way this operator works is as follow:

    let a = [1, 2, 3];
    let b = 4;
    let c = [...a, b]; // Here we will add each item of a and then b resulting in c = [1, 2, 3, 4]
    

    Because of this, the runtime will try to 'iterate' over state, but state is an object, not an array. That's the first error.

    Reducer return type: In the default case, you're returning the original state as expected, but in both cases you're actually returning an array.

    As a good practice, if you explicitly specify the return type of the reducer, you'll see how TypeScript complains about it.

    Solution: To fix your code, you should rewrite your reducer to something like:

    case AddToStateActionType.ADD_NAME:
        // Here you return a copy of state and overwrite the name property.
        return { 
            ...state,
            name: action.payload.name,
        };
    case AddToStateActionType.ADD_TO_WISHLIST:
        // Here you return a copy of state and overwrite the wishlist property with the same elements + the new one.
        return {
            ...state,
            wishlist: [...state.wishlist, action.payload.wishlist]
        };