Search code examples
angularreduxangular5fluxng2-redux

Using Redux how do you stop the switch in the reducer growing?


Using Redux how do you stop the switch in the reducer growing?

I'm using angular 5 and ng2-Redux.

I'm using the reducer and a switch but I'm worried that the switch will grow as I add more action types.

Currently I'm trying to use a red combine reducer to manage the growth of the state and the files:

I currently get this error:

Unhandled Promise rejection: Reducer "app" returned undefined during initialization. If the state passed to the reducer is undefined, you must explicitly return the initial state. The initial state may not be undefined. If you don't want to set a value for this reducer, you can use null instead of undefined. ; Zone: ; Task: Promise.then ; Value: Error: Reducer "app" returned undefined during initialization. If the state passed to the reducer is undefined, you must explicitly return the initial state. The initial state may not be undefined. If you don't want to set a value for this reducer, you can use null instead of undefined.

Seems like the rootReducer needs to return state but I'm using a combine reducer.

My Reducer Service code:

import { combineReducers } from 'redux';

export interface IAppState {
    counter?: number;
    user?: Object;
    numberOfMessages?: number;
}

export interface IAction {
    type: string;
    payload?: any;
}

export const INITIAL_APP_STATE: IAppState = {
    counter: 0,
    user: {},
    numberOfMessages: 0
};

export const appReducer = (state: IAppState, action: IAction): IAppState => {
    switch (action.type) {
        case 'INCREMENT':
        return  {
            counter: state.counter + action.payload,
            numberOfMessages: state.numberOfMessages
        };
    }
    return state;
};

export const userReducer = (state: IAppState, action: IAction): IAppState => {
    switch (action.type) {
        case 'DECREMENT':
        return  {
            counter: state.counter,
            numberOfMessages: state.numberOfMessages - action.payload
        };
    }
    return state;
};

export const rootReducer = combineReducers({
  app: appReducer,
  user: userReducer
});

This is my module export section for the redux setup (ERROR HERE I THINK????):

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

This is my dispatch method:

 public increment(): void {
    this.ngRedux.dispatch({
      type: '[app] INCREMENT',
      payload: 1
    });
  }

 public decrement(): void {
    this.ngRedux.dispatch({
      type: '[user] DECREMENT',
      payload: 2
    });
  }

Solution

  • You should split up your IAppState into different feature states:

    export interface IAppState = {
      auth: IAuthState;
      onboarding: IOnboardingState;
      feedback: IFeedbackState;
    };
    

    Then you build up your rootReducer from all the feature reducers using combineReducers from 'redux':

    export const rootReducer = combineReducers({
      auth: authReducer,
      onboarding: onboardingReducer,
      feedback: feedbackReducer
    });
    

    Your feature states, actions, reducers and epics will therefore be smaller and easier to maintain.

    You can add feature specific prefixes to your action names to ensure they are unique:

    export class CounterActions {
      static INCREMENT: string = '[COUNTER] INCREMENT';
      static DECREMENT: string = '[COUNTER] DECREMENT';
      static RANDOMIZE: string = '[COUNTER] RANDOMIZE';
    

    UPDATE: Your follow-up problem

    The solution is in the error message, read carefully:

    If the state passed to the reducer is undefined, you must explicitly return the initial state.

    You need to pass in the initial state as a default value:

    export const userReducer = (state: IUserState = INITIAL_USER_STATE, action: IAction): IAppState => {
                                       ^^^^^^^^^^^   ^^^^^^^^^^^^^^^^^
        switch (action.type) {
            case 'LOGIN':
            return  {
                userName: action.payload.userName
            };
        }
        return state;
    };
    

    Also, the userReducer only gets the IUserState slice of the state passed in.