2

In issue 303 in the Redux repo, Dan Abramov gives an example of a function that can wrap a store’s subscribe method in order to pass the previous state to the subscriber.

function observeStore(store, select, onChange) {
  let currentState;

  function handleChange() {
    let nextState = select(store.getState());
    if (nextState !== currentState) {
      currentState = nextState;
      onChange(currentState);
    }
  }

  let unsubscribe = store.subscribe(handleChange);
  handleChange();
  return unsubscribe;
}

For some reason, this doesn't work for me. The first time my onChange handler is called currentState is undefined, as I’d expect. However, on each subsequent state change, the properties of currentState are equivalent in value (==) to those of nextState. The two state objects aren’t the same object, though because nextState === currentState evaluates to false.

I’m likely missing something really obvious. How do I capture the previous state in a closure?

Luke
  • 22,826
  • 31
  • 110
  • 193
Justin Makeig
  • 2,097
  • 15
  • 29
  • `==` isn't "equivalence in value". You can easily check that `{} != {}`. See also: http://stackoverflow.com/questions/359494/does-it-matter-which-equals-operator-vs-i-use-in-javascript-comparisons – mik01aj Jan 15 '16 at 09:46
  • What I was trying to convey is that the two states are definitely different objects (`!==`), but the property that I changed is equal in the two instances. It’s like the observer sees two copies of the updated state, not the old and the new state. – Justin Makeig Jan 15 '16 at 17:31
  • Argh! I must be doing something else wrong in my application because http://jsbin.com/xebomi/edit?js,console illustrates that it works. (Well, with one modification to Dan’s original code.) – Justin Makeig Jan 16 '16 at 00:42

1 Answers1

3

The problem is that my reducer wasn’t actually creating a new instance of the state, i.e. I was mutating the state in place, rather than returning a new copy. My reducer code was something like:

const newState = Object.assign({}, state);
newState.my.prop = …;
return newState;

However, Object.assign and ES2015 destructuring assignment only do shallow copies, meaning they only affect one level deep. In my app, the state I was updating was three levels deep. I used a quick, dirty, ugly hack in my reducer to test my theory:

const newState = JSON.parse(JSON.stringify(state)); // <-- Yuck!
newState.my.prop = …;
return newState;

Don’t try that at home. This was my first clue, though. A proper implementation would use something like Facebook’s Immutable library to ensure each mutation results in a fresh clone.

Justin Makeig
  • 2,097
  • 15
  • 29
  • You can also use [Immutability Helpers](https://facebook.github.io/react/docs/update.html) in your reducers without need of using immutable-js throughout the project – antiplayer Mar 25 '16 at 15:51
  • I just got burnt by a state mutation too. I needed deep-freeze (npm) to find the issue. – Adrian Lynch Sep 02 '16 at 08:10