Search code examples
reactjsreduxreact-redux

React Redux combineReducers function calls my reducers 2 times when its exported and an additional time in the createStore function


I'm fairly new to Redux and I'm trying to understand why is the combineReducers function calling the reducers twice.

My reducer/index.js looks like this

  import {combineReducers} from "redux"

const AReducer = (state = "A", action) =>{
    console.log("In A reducer")

    switch (action.type){
        case "Alpha":
            return state + action.payload
        default:
            return state
    }
}

const BReducer = (state = "B", action) =>{
    console.log("In B reducer")
    switch(action.type)
        {
            case "Beta":
                return state + action.payload
            default:
                return state
        }
}

const allReducers = combineReducers({
    A : AReducer,
    B : BReducer
})

export default allReducers

and my store/index.js looks like this

import {createStore} from "redux";
import allReducers from "../Reducer"


const store = createStore(allReducers, 
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
  )

export default store

And the console log is displaying this

index.js:4 In A reducer
index.js:4 In A reducer
index.js:15 In B reducer
index.js:15 In B reducer
index.js:4 In A reducer
index.js:15 In B reducer

I only want to understand why it's behaving like this. I want a better grasp around what's going on in the background


Solution

  • First of all, let's print the action.

    import { combineReducers, createStore } from 'redux';
    
    const AReducer = (state = 'A', action) => {
      console.log('In A reducer, action: ', action);
    
      switch (action.type) {
        case 'Alpha':
          return state + action.payload;
        default:
          return state;
      }
    };
    
    const BReducer = (state = 'B', action) => {
      console.log('In B reducer, action: ', action);
      switch (action.type) {
        case 'Beta':
          return state + action.payload;
        default:
          return state;
      }
    };
    
    const allReducers = combineReducers({
      A: AReducer,
      B: BReducer,
    });
    
    const store = createStore(allReducers);
    

    The logs:

    In A reducer, action:  { type: '@@redux/INIT3.j.l.q.g.r' }
    In A reducer, action:  { type: '@@redux/PROBE_UNKNOWN_ACTIONn.x.t.b.s.j' }
    In B reducer, action:  { type: '@@redux/INIT3.j.l.q.g.r' }
    In B reducer, action:  { type: '@@redux/PROBE_UNKNOWN_ACTIONu.8.f.5.c.h' }
    In A reducer, action:  { type: '@@redux/INIT3.j.l.q.g.r' }
    In B reducer, action:  { type: '@@redux/INIT3.j.l.q.g.r' }
    

    Explanation

    I use AReducer's logs to explain, Breducer is also the same.

    combineReducers function calls assertReducerShape() function internally.

    assertReducerShape() function will invoke each reducer passed in the combineReducers function with a init action to check if the reducer has a valid returned value. This is how In A reducer, action: { type: '@@redux/INIT3.j.l.q.g.r' } log come.

    And, it also invokes each reducer with unknown actions to check if the reducer return the current state for any unknown actions unless it is undefined. This is how In A reducer, action: { type: '@@redux/PROBE_UNKNOWN_ACTIONn.x.t.b.s.j' } log come.

    When calling the createStore function, it will dispatch init action. So that every reducer returns their initial state. This effectively populates the initial state tree. This is how In A reducer, action: { type: '@@redux/INIT3.j.l.q.g.r' } log come. This process is mentioned in the documentation, See tips.

    Also take a look at the INIT and PROBE_UNKNOWN_ACTION action types in utils/actionTypes file.

    const randomString = () =>
      Math.random().toString(36).substring(7).split('').join('.')
    
    const ActionTypes = {
      INIT: `@@redux/INIT${/* #__PURE__ */ randomString()}`,
      REPLACE: `@@redux/REPLACE${/* #__PURE__ */ randomString()}`,
      PROBE_UNKNOWN_ACTION: () => `@@redux/PROBE_UNKNOWN_ACTION${randomString()}`
    }
    

    These action types are private, and used by redux internally, you don't need to handle them. You will see the dispatched INIT action in redux dev tools, don't be surprised.