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?
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/
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With