Skip to content
This repository has been archived by the owner on Jan 27, 2021. It is now read-only.

Using a prefilled state with namespaced reducers causes loss of the default store values. #99

Closed
NikitaKolokoltsev opened this issue Dec 11, 2018 · 1 comment · Fixed by #100
Labels

Comments

@NikitaKolokoltsev
Copy link
Contributor

NikitaKolokoltsev commented Dec 11, 2018

Hello!
I faced some issues when I was trying to use namespaced reducers while creating the redux store with prefilled data.

I've made a simple repo to illustrate the problem.

Briefly, the bug is appearing when you have some reducer, lets say viewReducer:

let defaultState = {
  design: 'flat',
  detalizationLevel: 'small'
};

export default function viewReducer(state, action) {
  state = {
    ...defaultState,
    ...state
  }

  ...

Which is appearing as a part of the settingsReducer:

const settingsReducer = namespaced('settings')(
  combineReducers({
    view: viewReducer,
    ...another reducers belonging to the settings section...
  })
);

const appReducer = combineReducers({
  settings: settingsReducer,
  ...there can be other reducers for other parts of the application...
})

So right now the store hierarchy is something like that:

{
  settings: {
    view: {
      design: 'flat',
      detalizationLevel: 'small'
    },
    ...
  },
  ...
}

So the issue itself: If you try to create redux store with prefilled state:

const prefilledState = {
  settings: {
    view: {
      design: 'material'
    }
  }
}

const store = createStore(appReducer, prefilledState);

Then it will result into the store with missing default values:

store.getState();
# => {
       settings: {
         view: {
           design: 'material'
         }
       }
     }

As you can see the detalizationLevel field is not present.

Possible solution

This issue exists because of the processAction() function's code.
According to the code of the redux's createStore() - redux/src/createStore.js function:

// 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 })

This action is a special unique redux-specific action which type starts with @@redux/....
This will trigger all reducers because it wouldn't find appropriate case for this action and it will initialze store with default values which are specified in the reducers.
But according to the code of processAction() - redux-subspace/src/actions/processAction.js function:

import hasNamespace from '../actions/hasNamespace'
import isGlobal from '../actions/isGlobal'

const processAction = (namespace) => (action, callback, defaultValue) => {
    if (!namespace || isGlobal(action)) {
        return callback(action)
    } else if (hasNamespace(action, namespace)) {
        return callback({...action, type: action.type.substring(namespace.length + 1)})
    } else {
        return defaultValue
    }
}

export default processAction

As this redux-specific actions are not namespaced, first two conditions in processAction() function will be false so all namespaced reducers will return ONLY(!!!) provided prefilledState and that will lead to some missing default values untill the user duplicates them in the prefilledState.

So I made a quick fix for it in the corresponding PR.
But I am afraid a bit of the fact that if it was written like so, then there were a reasons for it. So, please, tell me if I am missing something.

Context

Situation with prefilledState is quite common when using server side rendering with React, when you fetched data from the API and need to generate the store from which yout React-application will take the data to render.

Your Setup

package version(s)
redux 4.0.1
redux-subspace 3.0.0

Thank you guys for your really helpfull library!

@mpeyper
Copy link
Contributor

mpeyper commented Dec 12, 2018

To be honest, preloading half the state is never really a scenario I envisioned when writing redux-subspace (we don't even use initial state at all, so it was an afterthought at best to begin with).

I'll keep the rest pf the conversation in the PR so it doesn't get fragmented in multiple threads.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants