I saw someone write this code for a reducer:
export default function reducer(state, action) {
switch(action.type) {
case 'ADD_TODO':
Object.assign({}, state, {
todos: [{
text: action.text,
completed: false,
}, ...state.todos]
})
default:
return state;
}
}
Why not just do this?
case 'ADD_TODO':
return [{text: action.txt, completed: false}, ...state.todos]
What's the difference?
In Redux, there's a concept of Splitting Reducers which is used for better organization when it comes to deducing the new state. When you split up a reducer into multiple different reducers, then combine them with combineReducers, you essentially give each reducer a slice of state corresponding to the respective state it manages. For example, I could create a todo reducer to only every manage todos and only be aware of the todos inside state, and nothing else.
In the first snippet you've posted, the reducer is a 'master' reducer of sorts. The reducer function in that example is the reducer that handles everything in state, not just todos, and is aware of everything in state. That's why you need this code:
Object.assign({}, state, {
todos: [{
text: action.text,
completed: false,
}, ...state.todos]
})
This creates a copy of state to prevent mutation (via Object.assign), then updates the todos property of the cloned state with a new todo. In this 'master' reducer, the reducer is managing all state and thus must clone state and update the todo property.
In the second example, it would not work in this same reducer. That's because you're returning just an array as the new state. That would mean, whenever you added a todo, state would just become an array. Not good! Your second snippet would only work if you split the master reducer into more than one, and you had a separate reducer managing only todos. That means, that todo reducer would only get its respective 'slice of state'. It would only be aware of the todos property inside state, and nothing else in state:
export default function todos(state, action) { //This reducer is only aware of todos in state, nothing else.
switch(action.type) {
case 'ADD_TODO':
return [{text: action.txt, completed: false}, ...state.todos] //This will modify only the `todos` property of state, not the whole state
}
}
And in your master reducer file:
import todos from './your/todo/reducer/file';
combineReducers({
todos
});
Here, todos's state argument will only ever receive the todos property in state, it's slice. Nothing else. The first snippet would have received the whole entire state object as the state argument.
Related
I initially had my reducer as such, and it does not work (my updated redux state does not lead my component to update, my component's prop 'RecipeStore' is mapped onto the redux state's 'myRecipes' property)
const initialState={
myRecipes: []
}
export default function(state=initialState, action){
switch(action.type){
case SUBMIT_RECIPE:
const newState={...state}
newState.myRecipes.push(action.payload)
return newState
default:
return state;}}
I figured that my component did not rerender with redux store update because I mutated state, and you should never mutate state. So I rewrote the reducer as such:
const initialState={
myRecipes: []
}
export default function(state=initialState, action){
switch(action.type){
case SUBMIT_RECIPE:
console.log("reducer invoked")
const copyState={...state, myRecipes:state.myRecipes.concat(action.payload)}
return copyState
default:
return state;
}
}
And this time it worked - my component updates every time the redux store changes. However - my original reducer also created a copy of the original state using the spread operator, and only mutated the copy, why doesn't it work?
This is one of these situations where I'm glad that I solved the problem but am peeved that I don't know why.
Can someone lend me their thought?
What are the different ways of initializing a react redux store's initial global state? I see two ways this redux could set an initial global state
Let's say we have a single reducer and all the javascript is in one file.
function rootReducer(state = "Initial Reducer State!", action){
switch(action.type) {
case SET_TEXT:
return "Ignore this case statement it won't run"
default:
return state;
}
}
(1) I know you can use something like createStore(rootReducer, initialState).
const store = createStore(
rootReducer,
initialState
)
const initialState = {
text: "Initial Global State!"
}
(2) However, I noticed some repos setting an initialState to a blank object, but the redux store shows a global state has been populated. Example from this stackoverflow post: how to set initial state in redux
const store = createStore(
combineReducers({
text: rootReducer
}),
initialState
)
const initialState ={}
Resulting global store:
(1) outputs {text: "Initial Global State!"}
(2) outputs {text: "Initial Reducer State!"}
Why does #2 work the way it does?
When and where does it get set?
[answer comes from me playing around with redux and getting advice from a senior react-redux dev, Andrew Dushane]
When a store is created through redux, every reducer automatically gets triggered one time
There's an action dispatched when the store is created. That's how the initial state supplied in each combined reducer gets initialized in the store. If you check redux dev tools you'll see the first action dispatched is "##redux/INIT{something}"
In redux's documentation, near the end of the file, there is a dispatch({ type: ActionTypes.INIT })
See here https://github.com/reduxjs/redux/blob/master/src/createStore.js#L281-L284
TLDR
Because in example #2,
store is created
combineReducers({text: rootReducer}) gets set
rootReducer runs, because every reducer runs one time on store creation
Default return value is made, in this case "Initial Reducer state!"
text in ({text: rootReducer}) captures response
{text: "Initial Reducer State!"} gets stored as global state
Final Notes
if you were to set an initialState on the store, this always runs after all the reducers get dispatched one time.
I am running a simple Redux reducer and store, but for some reason the reducer is called by default. Is that normal?
const apiWrapper = (state = {}, action) => {
switch(action.type) {
case "HELLO":
console.log("hello");
return state;
break;
default:
console.log("default");
}
}
const { createStore } = Redux;
const store = createStore(apiWrapper);
store.dispatch({ type: "HELLO" });
The snippet above outputs:
"default"
"hello"
I am only expecting it to log hello, why is default there too? Here's a JSBin.
Redux internally dispatches a dummy action when creating the store in order to set up the initial state. Per the Redux documentation:
When a store is created, Redux dispatches a dummy action to your reducer to populate the store with the initial state. You are not meant to handle the dummy action directly. Just remember that your reducer should return some kind of initial state if the state given to it as the first argument is undefined, and you're all set.
So when your reducer is first called, state will be undefined thus your default value {} will be used. Also, it will go to the default case because you're not supposed to explicitly handle the action, and thus you get the console.log. Make sure to return the state in the default case to set up initial state correctly.
Just out of curiosity, the action type the first dummy call Redux does is "##redux/INIT" signifying that Redux is initializing the store and testing it. A similar thing happens with combineReducers to test for bad patterns in reducers. Specifically, in the source:
// When a store is created, an "INIT" action is dispatched so that every
// reducer returns their initial state. This effectively populates
// the initial state tree.
dispatch({ type: ActionTypes.INIT })
So the initial dispatch essentially gives each reducer its respective slice of state, and populates the initial state tree.
Redux is successfully storing and updating state. The reducers are seemingly working correctly. I'm able to use this.props.dispatch. However, when it actually comes to detailing that information (i.e. this.props.array I always seem to get undefined.
Reducer:
export default function array(state = {}, action) {
switch (action.type) {
case "UPDATE_ARRAY":
state.array = action.array
return state;
default:
return state;
}
}
State-aware component:
constructor(props) {
super(props);
this.state = {
array: array
}
}
--
self.props.dispatch({
type: 'UPDATE_ARRAY',
array: array
})
--
const mapStateToProps = (state) => {
return {
messages: state.messages,
array: state.array
};
};
export default connect(mapStateToProps)(Component);
This only seems to be able to save state btw when I define an empty array. This doesn't seem right, I thought the intention of Redux was a self-contained store? Updating a variable seems to defeat the purpose a bit.
Would appreciate any help.
export default function array(state = {}, action) {
switch (action.type) {
case "UPDATE_ARRAY":state={
...state,
array:action.array
}
return state;
default:
return state;
}
}
you should always update your state immutably,instead of mutating the current application state ,you should create another object and return that.State should be immutable ,only way to change the state is to create a new one.This helps to improve the performance of the application.
I am not sure if you application has more than one reducer or not, if it has, than you must be using combine reducer method .So to access state.array in mapsStateToProps is like this
const mapStateToProps = (state) => {
return {
messages: state.{reducer_name}.message,
array: state.{reducer_name}.array
};
};
in place of 'reducer_name' you have to specify the reducers_name which you have define in combine reducer
And last mapStateToProps return array ,in props not in component state.
which you can access in this way {this.props.array},you cant set component state in componentDidMount and in componentWillRecieveProps (in case of aysnc action).
Your component will receive array as a field in its props field. Your code assumes it's in the state field. So instead of:
this.state = {
array: array
}
you would just access this.props.array wherever in your code you need to use the array. You don't need to put it in the local state at all. Usually, you would use it in the render function, like in this example:
render()
{
return <div>The array contains {this.props.array.length} items.</div>
}
I wonder if you're confusing local state with the Redux store's state? Local state is what you get/set when you access this.state in your component code. Every component can have its own state object that it can read from and write to.
The Redux store's state is what's passed in to mapStateToProps. It's usually the entire state object of all the combined reducers in your top-level reducer (though if you only have one reducer function and are not using combineReducers, then the store state is identical to that single reducer's state).
I suggest choosing more descriptive variable names, so that your code will be more readable. It's hard to understand what your intentions are for your code with such generic names. For example, you could name your reducer something that indicates what it's for, like bookListReducer, and name the array you want to store and retrieve for what will go inside it, like books. Naming both your reducer and all your variables array makes it harder to read your code. This will help anyone who reads your code in the future - including, most importantly, you!, as well as future Stack Overflow readers of your future questions (and perhaps this one if you edit it).
I am not sure the following is your issues, but hope these will help:
export default function array(state = {}, {type, array}) {
switch (type) {
case "UPDATE_ARRAY":
return {...state, array};
default:
return state;
}
}
Your reducer should be pure, which you had is mutating the state.
constructor(props) {
super(props);
this.state = {
array: array // what is array?
}
}
Above constructor is not right. You should be able to access the array from this.props.array as your mapStateToProps
Do a console.log(this.props) in your render function or ComponentWillReceiveProps, see if you can something :)
I'm learning react-redux form the todo example in the docs and don't see why the id of the nextTodo is held in the actions rather than the reducer. Shouldn't this be considered state because it changes overtime as more todos are added? To me, the purpose of an action is to grab some input from the user and transform it to an action, not generate state. It is the reducer's job to create state and change it according to the actions given to it.
Action Code
let nextTodoId = 0
export const addTodo = (text) => {
return {
type: 'ADD_TODO',
id: nextTodoId++,
text
}
}
Reducer code
const todo = (state, action) => {
switch (action.type) {
case 'ADD_TODO':
return {
id: action.id,
text: action.text,
completed: false
}
...
}
That's because the reducer is expected to be a pure function. That is, if you run it multiple times with the same parameters, it would return the same result, and the state of the rest of the application would not change.
For that reason, the reducer can't determine the ID, as if it did, it would cause repeated runs to have different results (i.e. different return values).
The reducer's job isn't to create state. It's job is to get the existing state and a delta (i.e. an action) and return a new state. And it should do so reliably.