Warm tip: This article is reproduced from serverfault.com, please click

I get an error when i test my reducer created with redux

发布于 2020-11-29 21:26:09

I get this error:

   Reducer "menu" 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
   dont want to set a value for this reducer, you can use null instead
   of undefined.

   5 | import { rootReducer } from "./reducers/rootReducer";
   6 | 
>  7 | const reducers = combineReducers({
     |                  ^
   8 |   root: rootReducer,
   9 |   menu: menuReducer,
  10 |   searcher: searchReducer,

  at node_modules/redux/lib/redux.js:378:13
      at Array.forEach (<anonymous>)
  at assertReducerShape (node_modules/redux/lib/redux.js:371:25)
  at combineReducers (node_modules/redux/lib/redux.js:436:5)
  at Object.<anonymous> (src/store/store.ts:7:18)
  at Object.<anonymous> (src/store/actionCreator.ts:1:1)
  at Object.<anonymous> (src/store/initialState.ts:5:1)
  at Object.<anonymous> (src/store/reducers/rootReducer.ts:1:1)
  at Object.<anonymous> (src/tests/rootReducer.test.ts:1:1)

  console.error node_modules/redux/lib/redux.js:325
  No reducer provided for key "root"

  Test Suites: 1 failed, 1 total Tests:       0 total Snapshots:   0
  total Time:        1.175s Ran all test suites related to changed
  files.

when test this reducer (rootReducer.ts):

import { rootInitialState, Reducer, action } from "../initialState";

class RootReducer implements Reducer {
  private initialState: object;

  public constructor(initialState: object) {
    this.initialState = initialState;
  }

  private addNewText = (currentTexts: object, newText: object): object => {
    let newTexts: object = { ...currentTexts };
    newTexts[newText["name"]] = newText["text"];
    return newTexts;
  };

  private switchBodyScroll = (
    payload: "LOCK" | "UNLOCK"
  ): "LOCKED" | "UNLOCKED" => {
    if (payload === "LOCK") {
      document.getElementsByTagName("body")[0].classList.add("scroll-lock");
      return "LOCKED";
    } else {
      document.getElementsByTagName("body")[0].classList.remove("scroll-lock");
      return "UNLOCKED";
    }
  };

  public reduce = (
    state: object = this.initialState,
    action: action
  ): object => {
    if (action.type === "SWITCH_BODY_SCROLL") {
      return {
        ...state,
        bodyScrollState: this.switchBodyScroll(action.payload),
      };
    } else if (action.type === "SWITCH_MINIMAP_MODE") {
      return {
        ...state,
        minimap: {
          ...state["minimap"],
          mode: action.payload.minimapClassList,
          buttonClassList: action.payload.buttonClassList,
        },
      };
    } else if (action.type === "SET_THEMES") {
      return { ...state, themes: [action.payload.items] };
    } else if (action.type === "SET_CURRENT_PAGE") {
      return {
        ...state,
        currentPageId: action.payload.id,
        nextPage: action.payload.next,
        currentPage: [...action.payload.components],
      };
    } else if (action.type === "ADD_NEW_TEXT") {
      return {
        ...state,
        texts: { ...this.addNewText(state["text"], action.payload) },
      };
    } else if (action.type === "SET_PAGE_SEARCHER_VALUE") {
      return { ...state, pageSearcherValue: action.payload };
    } else {
      return state;
    }
  };
}

export const rootReducer = new RootReducer(rootInitialState).reduce;

for example, menuReducer.ts. It contains default state but anyway returns undefined:

import { menuInitialState, Reducer, action } from "../initialState";

class MenuReducer implements Reducer {
  private initialState: object;

  public constructor(initialState: object) {
    this.initialState = initialState;
  }

  public reduce = (
    state: object = this.initialState,
    action: action
  ): object => {
    if (action.type === "SWITCH_MENU_MODE") {
      return { ...state, compactMenuCurtain: action.payload };
    } else {
      return state;
    }
  };
}

export const menuReducer = new MenuReducer(menuInitialState).reduce;

with this test (rootReducer.test.ts):

import { rootReducer } from "../store/reducers/rootReducer";

it("rootReducer should return LOCKED with payload LOCK", () => {
  expect(
    rootReducer(undefined, { type: "SWITCH_BODY_SCROLL", payload: "LOCK" })
  ).toBe("LOCKED");
});

store.ts file looks like this:

import { combineReducers, createStore } from "redux";

import { menuReducer } from "./reducers/menuReducer";
import { searchReducer } from "./reducers/searchReducer";
import { rootReducer } from "./reducers/rootReducer";

const reducers = combineReducers({
  root: rootReducer,
  menu: menuReducer,
  searcher: searchReducer,
});

export const store = createStore(reducers);

I tried to do the same test with other reducers. I get the similar error. Of course i have checked out the menu reducer and tried to test other reducers like that. I get the similar error. Also, each reducer set initial state as default if that was not given. My app work in browser without any errors, I get that only with test. Any imported things are .ts files. Maybe, i just missed something - now i'm learning to work whit jest.

I get the same error even i just write a reducer to the console or get the type of this (menuReducer.test.ts):

import { menuReducer } from "../store/reducers/menuReducer";

it("rootReducer should return LOCKED with payload LOCK", () => {
  console.log(menuReducer);
  console.log(typeof menuReducer); // That's a method of class
});
Questioner
doopath
Viewed
0
doopath 2020-11-30 07:17:07

Ok, I've resolve this one. Anyway, this looks strange. If someone has the same stuff he needs a solution. The solution: for some reason i can't just import my reducer and use it as a default method or something like that. Formally it says: "Hey man, i haven't something to give it to reducer, just take an error that cannot help ya.". I initialized my state ( with imported store and call the getState() of it) and this is start working. Code is (rootReducer.test.ts):

import { store } from "../store/store";
import { rootReducer } from "../store/reducers/rootReducer";
import { action } from "../store/initialState";

describe("rootReducer test failed", (): void => {
  store.getState(); // Initialize state (looks like this boy allow to use reducers only when you call it)

  it("should return LOCKED with payload LOCK and option SWITCH_BODY_SCROLL", (): void => {
    const switchBodyScrollAction: action = {
      type: "SWITCH_BODY_SCROLL",
      payload: "LOCK",
    };

    const newState: object = rootReducer(undefined, switchBodyScrollAction);

    expect(newState["bodyScrollState"]).toBe("LOCKED");
  });
});

Tests are passed, but i dunno why i can't just use the reducer as a another method; by the way, other reducers behave the same.