-
-
Notifications
You must be signed in to change notification settings - Fork 15.3k
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
Why does a reducer needs a default state? #514
Comments
I think you're misunderstanding Redux.
There's only a single store in Redux application. You never use more than a single store. Please read the documentation and the examples, I think this will help clarify the confusion. https://redux.js.org/basics/reducers You will see that reducers manage independent parts of the state tree. Therefore they should be able to specify the initial state for these parts. |
To avoid repeating the documentation here, let's focus on code samples. Please feel free to take code from docs and examples, change it to what you think is a better API, and propose it here. |
Wooo... that answer was way too quick. Thanks a lot. If there is only one store, why is it up to the user to create it? Why is it not created by Redux itself? By providing a I'm fine with the code from the docs, and I'm totally ok with people who want to pass a default state inside the reducer, I just would like to remove the const INC = 'INC';
const DEC = 'DEC';
function increment() { return { type: INC }; }
function decrement() { return { type: DEC }; }
// Nope (well, it's not wrong but I don't like it)
function reducerAdd(state = 0, action) {
switch (action.type) {
case ADD: return state + 1;
default: return state;
}
}
function reducerSub(state = 100, action) {
switch (action.type) {
case DEC: return state - 1;
default: return state;
}
}
const store = createStore(combineReducers({up: reducerAdd, down: reducerSub}));
// Yep !
function reducerAdd(state, action) {
switch (action.type) {
case INC: return state + action.value;
default: return state;
}
}
function reducerSub(state, action) {
switch (action.type) {
case DEC: return state - action.value;
default: return state;
}
}
const reducers = combineReducers({up: reducerAdd, down: reducerSub});
const store = createStore(reducers, {up: 0, down: 100});
const anotherStore = createStore(reducers, {up: -1000, down: 1000}); |
It was too common source of mistakes. People would forget a Notice |
The pattern you described doesn't scale. If may be OK for five-function application, but for a bigger app, every time you're tweaking a reducer, you'll have to go to where its initial state is defined (separate file) and tweak the state's shape there. We want to co-locate reducer logic with its initial state shape so you don't forget to change one when you change another. |
Fair enough, it's only a helper, sorry to have bothered you with that and congratz on the 1.0 btw, awesome work. As for saying it doesn't scale, well, I'm personally fine with the fact that I also have to edit the initial state from the store. If the state architecture is changed, you will probably need to edit several other files anyway, the ones actually reading the state. In my project, I don't want beginners to think that by just tweaking the reducer and its default state, it will magically works in the rest of the app. I also like that by reading only one file, you can see the whole structure of the global state. But it's true that co-locating the logic and the state has its advantages too. |
This won't work for “dynamic” reducers anyway. From the async example: function posts(state = {
isFetching: false,
didInvalidate: false,
items: []
}, action) {
switch (action.type) {
case INVALIDATE_REDDIT:
return Object.assign({}, state, {
didInvalidate: true
});
case REQUEST_POSTS:
return Object.assign({}, state, {
isFetching: true,
didInvalidate: false
});
case RECEIVE_POSTS:
return Object.assign({}, state, {
isFetching: false,
didInvalidate: false,
items: action.posts,
lastUpdated: action.receivedAt
});
default:
return state;
}
}
function postsByReddit(state = { }, action) {
switch (action.type) {
case INVALIDATE_REDDIT:
case RECEIVE_POSTS:
case REQUEST_POSTS:
return Object.assign({}, state, {
[action.reddit]: posts(state[action.reddit], action)
});
default:
return state;
}
}
|
A reducer function needs an "accumulator", which in Redux is the state. If there is no accumulator value, then a reducer can't do anything. In functional programming, for example in Lisp or Clojure, you can reduce on an array of numbers and apply the Similarly, imagine you have a Redux reducer for Since a reducer that handles
Maybe this is just awkward phrasing, but that's not quite right. The store is a dumb container. It just holds onto the state. The store doesn't know anything about your app. It gets state either when you create it (optional) or as returned from reducers. Regarding keeping separate stores for normal data and admin data, you will get the exact same level of safety/separation by creating and composing separate reducer functions. The reducer function for your global state can't accidentally change the admin state, since it has absolutely no way to see it. Each reducer is given only the subset of the data for which it is responsible. I understand that in your case, you want to pass in the initial state, however, unless you are actually populating that state with non-default values stored (e.g., data from a database or in a web browser's local storage), I would suggest that you are better off just putting the defaults in each reducer, since it's necessary anyway. BTW, I wrote this as much for myself to confirm my understanding, as to try to answer your question, so anyone please let me know if I am mistaken anywhere. 😄 |
👍 |
Gotcha, thanks a lot for all the explanations guys! I will rethink how I am currently using Redux. |
@gaearon is it worth writing
instead of simply |
@jimmyn : that's semantically the same. The key issue is whether the incoming See Dan's explanation of how reducer default arguments and Redux |
Link is dead |
Updated to point to the current Redux docs page at https://redux.js.org/basics/reducers . |
I might be missing something, but I don't understand why a reducer should have a default state. Most of the doc have something like:
I don't see why the reducer should know anything about the default state. The state is managed by the store, the reducer is a pure function that does its job with whatever you give it as argument and not trying to guess what the state is if there is none. But well, I though that it was ok as long as I wasn't forced to put a default state inside the reducer.
Except that
combineReducers
forces me to put a default state. According to this code, anundefined
state will be triggered by Redux to the reducer. Why? My store will pass the default state, so it will never beundefined
in practice. My reducer is simple: if it knows the action, it will do something, if not, it will return the state. If the state isundefined
, it will returnundefined
.Even worse, what if I want to use the same reducer in several different stores with different initial states? Agreed I can always put a fake default state inside the reducer (like
0
) and it will be overridden by the store default states, whatever they are, but it feels just wrong.The text was updated successfully, but these errors were encountered: