Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

State in redux/react app has a property with the name of the reducer

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

like image 314
Karel Frajták Avatar asked Feb 27 '16 09:02

Karel Frajták


People also ask

Why are Redux state functions called as reducers?

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

What is reducer in React Redux?

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.

What is state in React Redux?

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.

What is state reducer in React?

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, ...]


Video Answer


2 Answers

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.

like image 169
Dan Abramov Avatar answered Oct 23 '22 23:10

Dan Abramov


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]
like image 34
Joshua Comeau Avatar answered Oct 24 '22 01:10

Joshua Comeau