Peter Szekeli
Peter Szekeli

Reputation: 2800

Redux / Angular getState() is undefined

I'd like to implement Redux in a simple Angular app. As a tryout, I'd like to make a simple todo list, but I struggle to make it work.

Here is a sample code:

app-module.ts

import { NgRedux, NgReduxModule } from '@angular-redux/store';
import { IAppState, rootReducer, INITIAL_STATE } from './store';
import { TodoListComponent } from './todo-list/todo-list.component';

@NgModule({
  declarations: [
    AppComponent,
    TodoListComponent
  ],
  imports: [
    ...
    NgReduxModule
  ],
...   
})

export class AppModule {
  constructor(ngRedux: NgRedux<IAppState>) {
    ngRedux.configureStore(rootReducer, INITIAL_STATE);
  }
}

store.ts

import { ADD_TODO } from './actions';
import { tassign } from 'tassign';

export interface IAppState {
  lastUpdate: Date;
  todos: Todo[]; 
  // I tried `any[]`, but it didn't help.
}

export const INITIAL_STATE: IAppState = {
  lastUpdate: null,
  todos: [] 

//tried todos: new Array() too, but didn't make a difference };

export function rootReducer(state: IAppState, action): IAppState {
    switch (action.type) {
    case ADD_TODO:
        return tassign(state, {
            todos: state.todos.concat(action.todo),
            lastUpdate: new Date()
        });
    }
    return state;
}

todo.ts

export class Todo {
  title: string;
}

todo-list-component.html

<h1>Todo List</h1>
<input type="text" #title>
<button (click)="addTodo(title)">Add</button>
<ul>
  <li *ngFor="let todo of todos | async">
    <span>{{ todo.title }}</span>
  </li>
</ul>

todo-list-component.ts

import { Component } from '@angular/core';
import { NgRedux, select } from '@angular-redux/store';
import { IAppState } from '../store';
import { ADD_TODO } from '../actions';
import { Todo } from '../todo';

export class TodoListComponent {
  @select((s: IAppState) => s.todos) todos: Todo[];

  constructor(private ngRedux: NgRedux<IAppState>) {
    ngRedux.subscribe(() => console.log(ngRedux.getState()));
  }

  addTodo(input) {
    if (!input.value) {
      return;
    }

    const todo = new Todo();
    todo.title = input.value;
    this.ngRedux.dispatch({
      type: ADD_TODO,
      todo: todo
    });

    input.value = '';
  }
}

and package.json

{
  "name": "angular-demo",
  "version": "0.0.0",
  "license": "MIT",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
  "private": true,
  "dependencies": {
    "@angular-redux/store": "^7.1.1",
    "@angular/animations": "^5.2.7",
    "@angular/cdk": "^5.2.3",
    "@angular/common": "^5.2.4",
    "@angular/compiler": "^5.2.4",
    "@angular/core": "^5.2.4",
    "@angular/forms": "^5.2.4",
    "@angular/http": "^5.2.4",
    "@angular/material": "^5.2.3",
    "@angular/platform-browser": "^5.2.4",
    "@angular/platform-browser-dynamic": "^5.2.4",
    "@angular/router": "^5.2.4",
    "core-js": "^2.4.1",
    "font-awesome": "^4.7.0",
    "hammerjs": "^2.0.8",
    "immutable": "^3.8.2",
    "redux": "^3.7.2",
    "rxjs": "^5.1.0",
    "tassign": "^1.0.0",
    "typescript": "^2.7.2",
    "web-animations-js": "^2.3.1",
    "zone.js": "^0.8.4"
  },
  "devDependencies": {
    "@angular/cli": "1.7.3",
    "@angular/compiler-cli": "^5.2.4",
    "@angular/language-service": "^5.2.4",
    "@types/jasmine": "2.8.6",
    "@types/node": "~9.4.6",
    "codelyzer": "~4.2.1",
    "jasmine-core": "~3.1.0",
    "jasmine-spec-reporter": "~4.2.1",
    "karma": "~2.0.0",
    "karma-chrome-launcher": "~2.2.0",
    "karma-cli": "~1.0.1",
    "karma-jasmine": "~1.1.0",
    "karma-jasmine-html-reporter": "^0.2.2",
    "karma-coverage-istanbul-reporter": "^1.2.1",
    "protractor": "~5.3.0",
    "ts-node": "~5.0.0",
    "tslint": "~5.9.1",
    "typescript": "~2.7.2"
  }
}

So when I type some text and hit Add, I'd expect it to be added to the todos list and displayed underneath the textbox.

But instead, I got a js error on the console, saying:

ERROR TypeError: Cannot read property 'todos' of undefined at TodoListComponent.addTodo (todo-list.component.ts:25) at Object.eval [as handleEvent] (TodoListComponent.html:4) at handleEvent (core.js:13547) at callWithDebugContext (core.js:15056) at Object.debugHandleEvent [as handleEvent] (core.js:14643) at dispatchEvent (core.js:9962) at eval (core.js:10587) at HTMLButtonElement.eval (platform-browser.js:2628) at ZoneDelegate.invokeTask (zone.js:421) at Object.onInvokeTask (core.js:4740)

If I check it in debugger, this.ngRedux.getState() is undefined, therefore it doesn't find the todos property. So it seems that the initialization goes wrong, but I don't see why.

Any ideas what I'm doing wrong?

Upvotes: 1

Views: 2410

Answers (1)

alsami
alsami

Reputation: 9815

You are missing the default return in your reducer function

export function rootReducer(state: IAppState = INITIAL_STATE, action): IAppState {
    switch (action.type) {
    case ADD_TODO:
        return tassign(state, {
            todos: state.todos.concat(action.todo),
            lastUpdate: new Date()
        });

     default: return state;
    }
}

Because initially there is no action dispatched and therefore the initial-state must be returned.

Upvotes: 2

Related Questions