Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Access other parts of state in Redux reducer

Tags:

reactjs

redux

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?

like image 428
Ben Smith Avatar asked Jan 06 '16 14:01

Ben Smith


1 Answers

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('on change equal?', store.getState().data === store.getState().originalData)
)

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

store.dispatch({ type: TOGGLE });

The console.log output should be:

on load equal? true
on change equal? false

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

like image 67
Dominic Avatar answered Oct 11 '22 20:10

Dominic