redux: I give the initialState in createStore and then combineReducers show one of my reducers returned undefined during initialization

in reducers:

const todoBlog = combineReducers({
  blogTypeVisibilityFilter,
  blogs
})

in blogTypeVisibilityFilter:

const blogTypeVisibilityFilter = (state, action)=>{
  switch (action.type) {
    case 'BLOG_TYPE_VISIBILITY_FILTER':
      return action.filter
    default:
      return state
  }
}

export default blogTypeVisibilityFilter;

in blogs:

const blogs = (state,action)=>{
  return state
}

in createStore:

const initialState = {
  blogTypeVisibilityFilter:'SHOW_ALL_BLOG',
  blogs:data.data,
}

const store = createStore(reducer,initialState,window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());

and then it shows wrong:

Reducer “blogTypeVisibilityFilter” 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.

but when i just change

const todoBlog = combineReducers({
  blogTypeVisibilityFilter,
  blogs
})

to

const todoBlog = (state={},action)=>{
  return{
    blogTypeVisibilityFilter:blogTypeVisibilityFilter(state.blogTypeVisibilityFilter,action),
    blogs:blogs(state.blogs,action)
  }
}

in reducers it runs well and without any errors

why i use combineReducers it goes wrong?

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Comments: 30 (13 by maintainers)

Most upvoted comments

This is a bug tracker, not a support system. For usage questions, please use Stack Overflow or Reactiflux. Thanks!

@JoseFMP :

Ah, I think I know what the issue is. This is specific to how combineReducers() works.

combineReducers expects that all of the “slice reducers” you give it will follow a couple rules. In particular, it expects that if your reducer is called with state === undefined, it will return a suitable default value. To verify this, combineReducers() will actually call your reducer with (undefined, {type : "SOME_RANDOMIZED_ACTION_TYPE"}) to see whether it returns undefined or a “real” value.

Your reducers currently do nothing except return whatever’s passed in. That means that if state is undefined, they will return undefined. So, combineReducers() is telling you you’re breaking the result that it expects.

Just change the declarations to something like configReducer = (config = {}, action), etc, and that will fix the warnings.

Again, to be clear: this is not a bug. This is behavior validation.

@Jhon-Snow-008 Multiple problems:

  1. Your accountReducer default case does not do return state.
  2. This is an issue that has been closed long ago, and the issue tracker is generally not meant for usage questions. Please ask on StackOverflow or in the Reactiflux chat instead.
  3. You shouldn’t even need to do that if you were writing modern Redux, but you are writing a style of Redux that’s outdated by many years. (Modern Redux with Redux Toolkit would be about 1/4 of the code you are writing here, with less error potential) Please follow the official Redux tutorial to learn modern Redux.

Modern Redux looks like this. Please don’t follow any tutorials showing you the legacy style:

import { configureStore, createSlice } from "@reduxjs/toolkit";

const accountSlice = createSlice({
  name: "account",
  initialState: {
    loan: 0,
    balance: 12,
    loanPurpose: "",
  },
  reducers: {
    deposit(state, action) {
      state.balance += action.payload;
    },
    withdraw(state, action) {
      state.balance -= action.payload;
    },
    payloan(state, action) {
      state.balance -= action.payload;
      state.loan -= action.payload;
    },
    requestLoan(state, action) {
      state.balance += action.payload.amount;
      state.loan += action.payload.amount;
      state.loanPurpose = action.payload.loanPurpose;
    },
  },
});
const { deposit, withdraw, payloan, requestLoan } = accountSlice.actions;
const accountReducer = accountSlice.reducer;

const store1 = configureStore({
  reducer: {
    account: accountReducer,
  },
});
store1.dispatch(deposit(10000));
console.log(store1.getState());

store1.dispatch(deposit(1000));
console.log(requestLoan({ amount: 400, loanPurpose: "For Car Buying" }));
console.log(payloan(500));

@markerikson Yes you are correct. I know already the documentation about the initial state. What I mean (and I do believe is the reason this issue was created and people also meant in this direction) is that it is counter-intuitive to provide “an initial state” when creating the store and still define an “default state” in the slice reducers (or root reducer). I.e. because very often, but not necessarily, they are the same, or very related, it feels counter-intuitive to have to define them twice. See as an exapmple the posts of @ElonXun or @Vittly who got confused same as me. I.e. my remark is not the API of Redux, is about how intuitive it feels using the API of Redux in this particular scenario, from a purely human perspective.

Notice that last paragraph is about the human feeling when using the API of redux. The machinery implementation or reasons behind it might be totally legit. But as an API consumer it feels confusing.

For instance, for me, when developing I very frequently have an initial state in my application. So Usually I need to type it twice. Once to plug it when I create the store and another time to distribute it as default value in the slice reducers. Of course many solutions for that. But the principle that for me as human makes it confusing is that I have to type twice the same thing.

However I do reckon having a special case in which the initial state is set and not making compulsory that the slice reducers or root reducer have a “default state” is more trouble some than still making it mandatory.

So the only contribution here is to mention that it feels a little bit counter-intuitive. But just that.

Thank you @markerikson . About the documentation: image

So if the reducer is called with an unknown action, I should return the same state as the reducer does not know what the action should do in this reducer.

When creating the store, Redux checks the reducers by sending them an unknown action and previous state undefined. So if the previous state was undefined the reducer should return undefined according to the documentation (because the reducer does not know the action). But If the reducer returns undefined, I guarantee you no Redux app could work.

For the example code:

import { createStore } from 'redux';

const configReducer = (config: any, action: any): any =>{
        return config;
}

const customReducer = (customData: any, action: any): any =>  {
        return customData;
}

const reducers = (currentState: IAppState, action: any): IAppState => {

    var appStateToEvaluate: any;
    if (currentState) { //needs to add this to pass the `undefined` check of redux
        appStateToEvaluate = currentState;
    }
    else{
        //why redux is doing this ?!
        appStateToEvaluate = {}
    }
    const newState: IAppState = {
        cvConfig: configReducer(appStateToEvaluate.config, action),
        personalData: customReducer(appStateToEvaluate.customData, action)
    }

    return newState;
}

const defaultConfig = "cool config";
const data = "yieaah some data";

var initialState = {config: defaultConfig, customData: data};
const store = createStore(reducers, initialState) // at this point redux calls all the reducers with 'undefined' state. Why?

With combineReducers, each slice reducer is expected to “own” its slice of state. That means providing an initial value in case the current slice is undefined, and combineReducers will point out if you’re not doing that.