Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Warn if the initial state shape doesn't match the reducer names #302

Merged
merged 1 commit into from
Jul 31, 2015
Merged

Warn if the initial state shape doesn't match the reducer names #302

merged 1 commit into from
Jul 31, 2015

Conversation

ellbee
Copy link
Contributor

@ellbee ellbee commented Jul 22, 2015

Ok, not super happy with this but it is a start.

  • I have written tests for verifyStateShape, but I couldn't think of a neat way to test the code in createStore so there is currently a hole in the test coverage.
  • I don't really like the function name verifyStateShape. Is stateShapeWarning better?
  • I haven't used an external warning module. Would it be a good idea? I'm currently just calling console.warn.

Happy to take some direction on this.

@ellbee ellbee changed the title Warn if the initial state shape doesn't match the reducer names WIP Warn if the initial state shape doesn't match the reducer names #272 WIP Jul 22, 2015
@gaearon
Copy link
Contributor

gaearon commented Jul 23, 2015

Let's do this in combineReducers instead?

@michalkvasnicak
Copy link
Contributor

@gaearon
Copy link
Contributor

gaearon commented Jul 23, 2015

I think we should change state = {} to state = mapValues(finalReducers, () => undefined) so if the state isn't rehydrated, the warning is skipped.

@michalkvasnicak
Copy link
Contributor

I think that it needs an extra check because typeof [] or typeof null return object too.

ellbee@0fee440#diff-bc52e10bb986be8700c6e212e9bc581cR22

@gaearon
Copy link
Contributor

gaearon commented Jul 23, 2015

We don't need to worry about this if the check is moved to combineReducers because the root state is then guaranteed to be an object.

@michalkvasnicak
Copy link
Contributor

@gaearon Yeah you are right, it needs to call verifyState after mapValues are done, otherwise it'll warn everytime. I did't get mapValues right at first look.

@gaearon
Copy link
Contributor

gaearon commented Jul 23, 2015

Hmm. The initial state can still be wrong. I guess we should just check isPlainObject on it, shouldn't we? If it's not a plain object something is already bad.

@michalkvasnicak
Copy link
Contributor

@gaearon I agree. Initial state should be checked to be a plain object.

@emmenko
Copy link
Contributor

emmenko commented Jul 23, 2015

PS: this is a duplicate of #276 . We can close mine, but maybe you can keep the failing test?

@ellbee
Copy link
Contributor Author

ellbee commented Jul 23, 2015

@gaearon Yes, no problem. I've moved the check to combineReducers.

'match shape of state object returned by reducer';
}

return '';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better return null IMO.

@ellbee
Copy link
Contributor Author

ellbee commented Jul 23, 2015

@gaearon I've hopefully addressed your comments in the latest commit

@emmenko Do you think we still need the failing test in createStore? I'll certainly add it if so.

warning(
verifyStateShape(state, finalState),
'Shape of initial state object given to createStore does not ' +
'match shape of state object returned by reducer'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By this point the user does not really define a reducer. All they do is pass reducers object (which they might get from import * as something ES6 import). Therefore they won't understand which reducer the warning message is referring to.

The warning should instead say something like Unexpected key "<key>" in the initialState will be ignored. Expected to find one of the known reducer keys instead: "oneReducer", "otherReducer", ....

@ellbee
Copy link
Contributor Author

ellbee commented Jul 23, 2015

Updated with better warning message

@gaearon
Copy link
Contributor

gaearon commented Jul 23, 2015

Looks really good. I'll test this in a day or two and get this into 1.0 release.

👍

@gaearon
Copy link
Contributor

gaearon commented Jul 23, 2015

Please rebase on breaking-changes-1.0 and squash this into a single commit.

@emmenko
Copy link
Contributor

emmenko commented Jul 23, 2015

@ellbee I think it's ok like this, just it's not really the complete flow. Usually you do createStore(reducer, initialState) and store.getState().
But I'm not sure if it's really necessary...

@gaearon
Copy link
Contributor

gaearon commented Jul 23, 2015

@emmenko Do you mean the tests? I don't think it's a problem because this is behavior completely specific to combineReducers.

@ellbee
Copy link
Contributor Author

ellbee commented Jul 23, 2015

Ok, thanks @gaearon! Rebased and squashed.

@emmenko
Copy link
Contributor

emmenko commented Jul 23, 2015

👍

import mapValues from '../utils/mapValues';
import pick from '../utils/pick';
import invariant from 'invariant';
import { ActionTypes } from '../createStore';
import warning from 'warning';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: let's move invariant here so their imports are together.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move invariant next to warning in the import order? Can do, I just put them into alphabetical order.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, done

@gaearon gaearon changed the title Warn if the initial state shape doesn't match the reducer names #272 WIP Warn if the initial state shape doesn't match the reducer names Jul 24, 2015
@gaearon
Copy link
Contributor

gaearon commented Jul 24, 2015

I played with this a little bit more, and I think we could still improve this a little bit:

  • When the initial state is plain object, there is no warning. What I meant is, we should always warn if the initial state is not a plain object: that's a symptom of a mistake. There is no way for combineReducers to do anything useful with something other than a plain object.
  • If I remove export default from todos.js in todomvc example, I get a cryptic error message: Unexpected key "todos" in the initialState will be ignored. Expected to find one of the known reducer keys instead: "". What does "" mean? It means Object.keys(finalReducers).length === 0. But it's definitely a symptom of a mistake! Evidently the user to forgot to export any reducer, so we should probably have a better warning in this situation.
  • The check is being performed at every invocation, not just at the initial one. This feels like a waste even in development environment. Can we only emit the warning the first time the function is invoked? I understand it makes it impure, but it's no big deal in development environment: we just want to warn (and check) only once. Does this make sense?

@emmenko
Copy link
Contributor

emmenko commented Jul 24, 2015

Good points!

Can we only emit the warning the first time the function is invoked?

@emmenko emmenko closed this Jul 24, 2015
@emmenko emmenko reopened this Jul 24, 2015
@emmenko
Copy link
Contributor

emmenko commented Jul 24, 2015

Sorry, my finger slipped...

I wanted to ask if it would be a problem with hot reloading?

Can we only emit the warning the first time the function is invoked?

@gaearon
Copy link
Contributor

gaearon commented Jul 24, 2015

I don't think this would be a problem? On hot reloading the function will be re-generated so the code will run again.

@emmenko
Copy link
Contributor

emmenko commented Jul 24, 2015

Ok, just wanted to make sure :)

@ellbee
Copy link
Contributor Author

ellbee commented Jul 25, 2015

Updated pull request to add extra warnings and only call verifyStateShape once as suggested. Ideas for improvements to the warning messages are welcome!

I have been wondering though if these new warnings should be invariants instead?

@ellbee
Copy link
Contributor Author

ellbee commented Jul 30, 2015

@gaearon I'll have some spare time later today, is there anything else you would like me to do with this PR?

@gaearon
Copy link
Contributor

gaearon commented Jul 30, 2015

@ellbee

I don't have time to look at it right now, but please do what I'd do:

  • Take a sample app
  • Turn your mind off and make stupid mistakes with initialState, exporting reducers, export default, etc
  • See if the warnings help you or not

@ellbee
Copy link
Contributor Author

ellbee commented Jul 30, 2015

@gaearon OK, will do. Thanks!

@gaearon gaearon merged commit fca60c8 into reduxjs:breaking-changes-1.0 Jul 31, 2015
@gaearon
Copy link
Contributor

gaearon commented Jul 31, 2015

Hey, awesome job. Thanks.

@nuragic
Copy link

nuragic commented Aug 26, 2015

Hi! I just get this warning and I don't understand what's the problem with this one..

I mean, I have an initial state from the server that looks like this for example:

{
  foo: [/* ... */], // ok, I've a reducer called 'foo'
  bar: true,        // no reducer
  baz: false,       // same here

}

Should I have one reducer for every single state property in order to access it from my components?

@gaearon
Copy link
Contributor

gaearon commented Aug 26, 2015

Yes, if you use combineReducers, because that's exactly what it does. It lets you assign a single reducer to each state property.

You can also write the root reducer by hand, without combineReducers. Then it's up to you how to structure it.

https://github.com/rackt/redux/blob/master/docs/api/combineReducers.md

https://github.com/rackt/redux/blob/master/docs/basics/Reducers.md

@nuragic
Copy link

nuragic commented Aug 26, 2015

Thanks for the quick response!

Ok, point taken... Maybe I need to think a bit more about my app state in order to know if I need the 1:1 relation between state properties and reducers... Another option could be to have 1 reducer to manage the entire state right?

@gaearon
Copy link
Contributor

gaearon commented Aug 26, 2015

Yes. In fact that's what combineReducers generates — a single reducer. (And instead of using combineReducers you can write it by hand.)

@nuragic
Copy link

nuragic commented Aug 26, 2015

Ok, I'll evaluate if I need to write my own or simply use one "master" reducer (I'm doing a small app btw eheh).

Many thanks for the support! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants