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

Created on 23 Aug 2017  ·  27Comments  ·  Source: reduxjs/redux

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?

Most helpful comment

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

All 27 comments

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

I have same problem.

Steps:

  1. Image simple store shape { foo, bar }.
  2. Create simple reducer like simpleReducer = state => state.
  3. Combine reducers in reducers = combineReducers({ foo: simpleReducer, bar: simpleReducer }).
  4. Create state with initial value with createState(reducers, { foo: Obj1, bar: Obj2 }).
  5. Get error Reducer "foo" returned undefined during initialization. If the state passed to the reducer is undefined, you must explicitly return the initial state

Reason is assertReducerShape that run all reducers with undefined state to get initial state parts. My simple reducer returns its argument which is undefined in that case. Why Obj1 from createState call is not considered as initial state of foo-part?

@timdorr can you reopen issue?

Workaround is to make simpleReducer = (state = null) => state. But is it ok?

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.

'Initial state' has multiple meanings. They are the default return value of reducer and the second argument of createStore. I think there is a problem. Can you explain why does combineReducers check reducers for initialState and createState don't?

@Vittly : combineReducers is deliberately opinionated, while createStore is not. createStore simply calls whatever root reducer function you've given to it, and saves the result. combineReducers assumes that if you're using it, you're buying into a specific set of assumptions about how the state should be organized and how the combined reducers should behave.

You might want to read through #191 and #1189, which go into the history and details of why combineReducers is opinionated and what opinions it has.

So the issue is (shortly) "if you combine reducers you also divide your store tree and you may miss something or deliberately preload only part of store in createStore 2nd arg. To make store tree more consistent use assertReducerShape". Okay, thanks for refs

Same problem here.
So I use createStore and pass an initial state. But in reduct.js file, function assertReducerShape redux will check my reducers when passing an undefined state.

IMHO this is a bug.

@JoseFMP : I would effectively guarantee that whatever you're seeing is _not_ a bug. However, if you can put together a repo or CodeSandbox that demonstrates the issue, we can take a look.

Sure. I filed an issue in issue in Stackoverflow:
https://stackoverflow.com/questions/53018766/why-redux-reducer-getting-undefined-instead-of-the-initial-state

The issue is pretty straightforward. So if I set an initial state when creating the store... why would redux want to evaluate the reducer with a undefined state? That makes indeed all the reducers to return a new state undefined.

But is nonsense, since we pass an initial state.
So this is failing:

import { combineReducers, createStore } from 'redux';

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

const customData = (customData: any, action: any): any =>  {
        return customData;
}
const reducers = combineReducers({config: configReducer, customData: customDataReducer})

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?

@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.

Hej @markerikson thank you for your response.
A few comments:

1) No, it is NOT specific of combineReducers . If I do not use combineReducers and implement my own root reducer, the same happens.

2) The inconsistency here is that, in the Redux documentation it is mentioned that if the reducer is called with a status that is unknown or unexpected, it should not modify the status and return the value that came as argument. I.e. ... if it receives undefined it should then return undefined. But another rule says the reducer should never return undefined so these two rules, as are currently in the redux documentation are inconsistent. And inconsistent with the implementation.

@JoseFMP : as I said previously, if you can provide a CodeSandbox that specifically demonstrates the issue, with comments pointing out exactly what you expect to happen vs what is actually happening, I can maybe take a look. (Also, please point to the docs sections that you feel are inconsistent.) Until then, I can only chalk this up to a misunderstanding of how Redux works internally.

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?

@JoseFMP : I think the key difference you're missing here is that in that example, the reducer function has _already_ handled the case where state is undefined, by using the ES6 default arguments syntax:

function todoApp(state = initialState, action) {

So the advice in the tutorial is correct - a reducer _should_ always return the existing state, _assuming the undefined case has already been handled_.

@markerikson
Thank you for your answer.
That is clear for me. Just missing in the tutorial. In the tutorial it does not say that the case to return is the one specified as default parameter value in the reducer. So the sentence you made italic is correct. That is that. My concern is that it is missing in the tutorial. Or I did not find it, or is ambiguous.

Now, independently of the tutorial and/or documentation, I find this undefined check personally not making sense for the specific case in which an initial state is specified. If no initial state is specified I find it is ok. Now this is not a discussion, just my opinion: If an initial state is specified I find it pointless to make this check. But I can (and have to) live with it anyway ;)

Thank you for your support Mark.

We can certainly try to reword some of the phrasing as part of our larger docs revamp (#2590 ).

That said, one of the original ideas behind Redux was that each "slice reducer" is responsible for "owning" its piece of the state, which includes both updates and providing an initial value. You seem to be kind of stuck on the aspect of "well, I'm providing an initial value to createStore", but you're discounting the expectations for how Redux expects it should behave even if you _aren't_ providing the value separately.

Somewhat surprised I didn't link this yet, but you might want to read through the Initializing State page in the docs.

@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.

I don't recall combineReducers calling assertReducerShape in earlier versions. In my code undefined is an invalid state of my reducers. I do not need combineReducers to ensure this for me, I need it to "combine reducers" and re-create the root object if something changes. It's going a bit beyond what I would expect it to do. IMO, it's too opinionated now.

Admittedly I haven't used combineReducers in some time as I haven't needed it in the core application I've been developing. But I'm now trying to wrap that application, and figured combineReducers was fine to use to slice the application up. The behavior of assertReducerShape is surprising.

@lukescott : that check has been there since September 2015:

https://github.com/reduxjs/redux/commit/a1485f0e30ea0ea5e023a6d0f5947bd56edff7dd

And yes, combineReducers() is _deliberately_ opinionated. If you don't want those warnings, it's easy enough to write your own similar function without those checks.

As a specific example, see Dan's gist Redux in 100 lines without the checks.

I get it. The old version passed the initial state and checked for undefined on each run (if I recall). What's surprising is the initial state is not passed on the first run. Passing undefined is an error in my app. As I said, I've been working on this project for a while, and haven't used combineReducers for a while. I'm just now getting back into using it, placing our application inside a wrapper.

I also get that it's opinionated. But that opinion was "a reducer must not return undefined" - which is the rule I abide by. It has changed to "we will pass undefined and you must create the state yourself from nothing". combineReducers no longer works with "there is always an initial state" - which is unfortunate. That drastically changes the rules that was in place 3 years ago.

I'm really not sure what behavior changes you're referring to. Can you point to specific examples?

The Initializing State docs page lays out the interactions between the preloadedState argument for createStore, the state = initialState handling for a reducer, and combineReducers(). That's based on a Stack Overflow answer that Dan wrote in early 2016, and nothing meaningful has changed in that regard that I know of.

@markerikson You're right. I looked back as far as 2.0, and it looks like it has always done this. Perhaps the complexity of my application has just made it more apparent.

The issue I have is that my reducer is defined as:

reducer(state: State, action: Action)

Meaning that state must not be undefined. That means there has to be an initial state. But because combineReducers calls reducer(undefined, INIT) to check it, it causes an error in my code (if it succeeds it later calls reducer(initState, INIT) - calling INIT twice).

This means any reducer used in combineReducers MUST be defined as:

reducer(state: State | undefined, action: Action)

So my assertion it didn't do that before is incorrect. But the issue I have, and the OP has, are likely the same: It forces you to declare your reducers with an optional state. I don't actually get the warnings, it causes my code to crash because it expects a non-undefined state.

If you say it must be this way and I need to roll it myself, that's fine. It's just unfortunate because in addition to asserting the shape, it already checks for undefined at run-time. Asserting the shape seems a bit overkill and counter-productive.

Yes. As described in that docs page, when you use combineReducers, the expectation is specifically that each slice reducer is responsible for returning its own initial state, and it should do that in response to sliceReducer(undefined, action). From the point of combineReducers, your code _is_ buggy because it is not adhering to the expected contract, and therefore it's telling you that code is wrong.

Are you not actually providing an initial state? What does the code actually look like?

I am providing an initial state through createStore. But that initial state is not being passed to my reducer because assertReducerShape is explicitly calling my reducer with undefined, despite me passing an initial state:

https://github.com/reduxjs/redux/blob/231f0b32641059caab3f98a3e04d3afaad19a7d1/src/combineReducers.js#L68

My code isn't buggy. It just requires that undefined is _never_ passed into it. An initial state is required and is generated from the server. combineReducers just breaks that contract and makes strictly typing the reducer with a required state impossible. I can do this without combineReducers and it works fine. I guess that's what I will have to do - but IMO - forcing an optional state is undesirable, and in my case makes combineReducers useless because it breaks my strictly typed application.

What I don't understand is why assertReducerShape is necessary. It already checks for undefined here at runtime:

https://github.com/reduxjs/redux/blob/231f0b32641059caab3f98a3e04d3afaad19a7d1/src/combineReducers.js#L169

assertReducerShape seems kind of redundant. But in my case, breaks type guarantees... which is the thing it's trying to assert?

So, this is really a TypeScript interaction issue, then?

To be honest, this seems as if it's ultimately a difference in approaches.

combineReducers() was written in JS, and is trying to do runtime checks.

You're trying to do static checks in TS, and the declaration you've written does not match how combineReducers() actually works. So, in that sense, the type declaration for your slice reducer is incorrect, because it _can_ and _will_ be called with undefined when used with combineReducers().

The specific line you've called out is checking that the return value of your slice reducer is not undefined when called with a (presumably meaningful) existing state slice value, whereas assertReducerShape() is checking that it doesn't return undefined when given an initial state value of undefined (ie, that the reducer self-initializes its state) and also when called with an unknown action type (ie, that the reducer always returns the existing state by default).

if you're getting undefined, then replace it... in my case, when my server reply without the data for any reason, maybe because there is not active session, I got that problem, so doing this in the reducer, worked as a charm for me:

export default function itemReducer(state = initialState.items | undefined, action){ 
    switch(action.type) 
   { 
         case "LOAD_ITEMS_SUCCESS":
               if(action.items==undefined) { 
                        action.items=[]; 
               }
                return action.items

If the reducer gets undefined, then replace undefined for an empty array, and you won't get that error anymore.

Cheers!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

CellOcean picture CellOcean  ·  3Comments

dmitry-zaets picture dmitry-zaets  ·  3Comments

caojinli picture caojinli  ·  3Comments

parallelthought picture parallelthought  ·  3Comments

captbaritone picture captbaritone  ·  3Comments