Ben Smith Ben Smith - 3 months ago 14
React JSX Question

Access other parts of state in Redux reducer

I'm using Redux and would like to set a flag in the state if the user has modified data.

First I am hydrating my store with some data from the server:

let serverData = {toggle: true}
const store = configureStore({data: serverData, originalData: serverData})


I then have a few reducers:

function data(state = {}, action) {
switch (action.type) {
case TOGGLE:
return {
...state,
toggle: !state.toggle
}
default:
return state
}
}

function changed(state = false, action) {
// This doesn't work; we are actually comparing state.changed.originalData and state.changed.data
return isEqual(state.originalData, state.data)
}

const rootReducer = combineReducers({
data,
originalData: (state={}) => state,
changed
})


What I'd like to do here is set the
changed
flag to true if
state.data
no longer equals
state.originalData
. However, using combineReducers, I can't access these parts of the state inside my
changed
reducer.

What's the most idiomatic way to do this?

Answer

Michelle is right, to expand on it in traditional flux terms think of a reducer as a store, you just called one of your stores "data" and another "changed" - that's not semantic and shows you probably are confused about how they're used.

Your changed reducer is supposed to either return the unmodified state, or, if an action was made that it's interested in, return the next state. It isn't for figuring out if your store changed - that's what subscribe is for.

const unsubscribe = store.subscribe(() =>
  console.log('store was changed:', store.getState())
)

When you call combineReducers it will call each reducer once, with an action of @@redux/INIT -- but don't concern yourself with that as it's internal. The important thing to understand is that under each reducer key, the state is initialised. In your case you initialised data to an empty object, changed to false, and originalData to an empty object.

Next when you called createStore you said "Update the 'data' and 'originalData' stores with this initial state from the server". So now the state of the 'data' and 'serverData' stores are equal to {toggle: true}.

At this point both objects are the same.

Subsequently when you call store.dispatch all your reducers will be called so that they can have a chance to modify state (and return a new object) under each reducer key. Each reducer only has access to it's own state. Therefore in your changed reducer you can't check for the overall object equality of your entire state like that.

However you can check for object equality in the subscribe (mixed ES5/6 here for Chrome 47)

'use strict';

const serverData = {toggle: true};
const TOGGLE = 'TOGGLE';

function data(state, action) {
    state = state || {};
  switch (action.type) {
    case TOGGLE:
        return Object.assign({}, state, {
        toggle: !state.toggle
      })
    default:
      return state
  }
}

function originalData(state) {
    state = state || {};
  return state;
}

const rootReducer = Redux.combineReducers({
  data,
  originalData
});

const store = Redux.createStore(rootReducer, {data: serverData, originalData: serverData });

const unsubscribe = store.subscribe(() =>
  console.log('equal?', store.getState().data === store.getState().originalData)
)

console.log('equal?', store.getState().data === store.getState().originalData);

store.dispatch({ type: TOGGLE });

The console.log output should be:

equal? true
equal? false

Fiddle: https://jsfiddle.net/ferahl/5nwfqo9k/1/