I am creating an app using Redux and React. I run into a problem where I cannot map state to component properties since the state has a property that matches the name of the reducer I used.
The root reducer is created with combineReducers
method
const rootReducer = combineReducers({
appReducer
});
The initial state is
const initialState = {
sources: [],
left: {},
right: {},
diff: {}
}
However in the component function mapStateToProps
:
function mapStateToProps(state) {
return {
sources: state.sources
}
}
The state.sources
is undefined
because the value of state
parameter is
{
appReducer: {
sources: [],
left: {},
right: {},
diff: {}
}
}
Is this a feature of redux? So when I use more reducers, all of them will add new property to state
variable? Or is there something wrong on my side (I never noticed this behavior in redux tutorials).
Thanks
Reducers do not just return default values. They always return the accumulation of the state (based on all previous and current actions). Therefore, they act as a reducer of state. Each time a redux reducer is called, the state is passed in with the action (state, action) .
Reducers are the only way to change states in Redux. It is the only place where you can write logic and calculations. Reducer function will accept the previous state of app and action being dispatched, calculate the next state and returns the new object.
Redux is a state container for JavaScript apps, often called a Redux store. It stores the whole state of the app in an immutable object tree. To create a store the createStore(reducer, [initialState], [enhancer]) function is used to create a new store.
The reducer function uses the action object and performs a state update, returning the new state. React then checks whether the new state differs from the previous one. If the state has been updated, React re-renders the component and useReducer() returns the new state value: [newState, ...]
If you only have a single reducer, you don’t need combineReducers()
. Just use it directly:
const initialState = {
sources: [],
left: {},
right: {}
}
function app(state = initialState, action) {
switch (action.type) {
case 'ADD_SOURCE':
return Object.assign({}, state, {
sources: [...state.sources, action.newSource]
})
case 'ADD_SOURCE_TO_LEFT':
return Object.assign({}, state, {
left: Object.assign({}, state.left, {
[action.sourceId]: true
})
})
case 'ADD_SOURCE_TO_RIGHT':
return Object.assign({}, state, {
right: Object.assign({}, state.right, {
[action.sourceId]: true
})
})
default:
return state
}
}
Now you can create a store with that reducer:
import { createStore } from 'redux'
const store = createStore(app)
And connect a component to it:
const mapStateToProps = (state) => ({
sources: state.sources
})
However your reducer is hard to read because it update many different things at once. Now, this is the moment you want to split it into several independent reducers:
function sources(state = [], action) {
switch (action.type) {
case 'ADD_SOURCE':
return [...state.sources, action.newSource]
default:
return state
}
}
function left(state = {}, action) {
switch (action.type) {
case 'ADD_SOURCE_TO_LEFT':
return Object.assign({}, state, {
[action.sourceId]: true
})
default:
return state
}
}
function right(state = {}, action) {
switch (action.type) {
case 'ADD_SOURCE_TO_RIGHT':
return Object.assign({}, state, {
[action.sourceId]: true
})
default:
return state
}
}
function app(state = {}, action) {
return {
sources: sources(state.sources, action),
left: left(state.left, action),
right: right(state.right, action),
}
}
This is easier to maintain and understand, and it also makes it easier to change and test reducers independently.
Finally, as the last step, we can use combineReducers()
to generate the root app
reducer instead of writing it by hand:
// function app(state = {}, action) {
// return {
// sources: sources(state.sources, action),
// left: left(state.left, action),
// right: right(state.right, action),
// }
// }
import { combineReducers } from 'redux'
const app = combineReducers({
sources,
left,
right
})
There is no large benefit to using combineReducers()
instead of writing the root reducer by hand except that it’s slightly more efficient and will likely save you a few typos. Also, you can apply this pattern more than once in your app: it’s fine to combine unrelated reducers into a single reducer several times in a nested way.
All this refactoring would have no effect on the components.
I would suggest you to watch my free Egghead course on Redux which covers this pattern of reducer composition and shows how combineReducers()
is implemented.
Actually, I believe your initial state would be:
{
appReducer: {
sources: [],
left: {},
right: {},
diff: {}
}
}
This is because combineReducers
works by taking the name of the reducer, and mapping its contents to that name.
Also, just a note, but if you're going to use more than 1 reducer, the names of your reducers should be more specific than appReducer
, and (just my personal opinion) they don't need the word reducer
. A typical app might look like this:
combineReducers({
user: userReducer,
messages: messagesReducer,
notifications: notificationsReducer
});
Then, your state could be accessed like:
state.user.email
state.messages[0]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With