Why
store.subscribe(() => console.log(store.getState()));
and not
store.subscribe(state => console.log(state));
?
Yeah I should probably do that.
I wanted to bike shed a little — maybe worth also adding previousState as a second parameter so consumer can compare reference without keeping the local state copy?
e.g.
store.subscribe((state, previousState) => {
if (selectStuffIWant(state) !== selectStuffIWant(previousState)) {
doSomething(state);
}
});
previousState as a second parameter so consumer can compare reference without keeping the local state copy?
Just a reminder how this is where Rx shines:
store.subscribe(state => console.log(state));
or
store.pairwise().subscribe((previousState, state) => {
console.log(previousState);
console.log(state);
});
Haha, I know. :-)
This is why I didn't bother to pass it into the callback initially. People who want to work directly with subscribe API will probably want to wrap it into an observable anyway..
In fact Redux createStore API can probably be implemented with two or three lines in Rx.
My intention was to extract a subset that can be used by people who don't want Rx dependency and want some opinionated wiring defaults (one root reducer, actions as plain objects) to get some benefits (logging, record/replay, time travel) “by default”.
Best to keep this a low level API. Passing current state to subscriber seems reasonable, though. For anything more complicated, it takes two lines to convert to observable https://github.com/acdlite/redux-rx/blob/master/src/observableFromStore.js
OK I'll accept a PR passing the parameter. One tiny requirement is to make sure it works correctly when Redux store is instrumented by Redux DevTools. I have a bad feeling that every store extension that “lifts” getState will also have to take special care of this parameter. Which is not fun and feels like the price you pay for making low-level APIs more consumer friendly.
Ah yes that's an excellent point. In that case, I'd say leave it as-is.
Please pass both current and previous state to listeners. I really don’t want to have to keep a reference to the previous state outside the listener and cannot afford to include Rx into my project.
I have asked in Slack channel exactly the same question as the one at the beginning of this issue. My confusing is: why a listener is forced to know about store (or anything else with getState() method) at all?
The store API is meant to be extensible. This is why it _needs_ to be as orthogonal as possible. The methods shouldn't duplicate functionality because otherwise creating extensions to it like Redux DevTools becomes harder. If extensions want to do something to the state before passing it to the consumer, they'll now have to do it in two different places (three if we add two parameters!) which is error prone.
The solution is simple: don't use subscribe directly! It's a low level API. If you want it to behave as an Observable, write a function that turns it into an observable! It isn't hard to do:
function toObservable(store) {
return {
subscribe({ onNext }) {
let dispose = store.subscribe(() => onNext(store.getState()));
onNext(store.getState());
return { dispose };
}
}
}
If you don't want to use RX and prefer a callback with previous and next value, again, easy to do. You can even do selecting as part of it:
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;
}
We can provide either as a utility inside Redux, if we can converge on the API people want.
What we _can't_ do is make Store interface less low-level and duplicating functionality between methods, because then building extensions on top of it becomes way harder.
The solution is simple: don't use subscribe directly! It's a low level API.
:+1: That's enough clarifications to me, I'd be ready to close this issue.
@gaearon , thanks for the elaborate clarification!
I'll keep this open to make sure I don't forget to add this to the docs.
Related:
How about:
store.subscribe(
state => console.log(state),
(previousState, state) => previousState.something !== state.something
);
In other words, being able to pass an optional method to store.subscribe that will provide a check against previous state.
That's the usual scenario anyway.
This will probably make things a little less dirty in the long run.
Closing for the reasons described here: https://github.com/gaearon/redux/issues/303#issuecomment-125184409. This is documented:
It is a low-level API. Most likely, instead of using it directly, you’ll use React (or other) bindings. If you feel that the callback needs to be invoked with the current state, you might want to convert the store to an Observable or write a custom observeStore utility instead.
Most helpful comment
The store API is meant to be extensible. This is why it _needs_ to be as orthogonal as possible. The methods shouldn't duplicate functionality because otherwise creating extensions to it like Redux DevTools becomes harder. If extensions want to do something to the state before passing it to the consumer, they'll now have to do it in two different places (three if we add two parameters!) which is error prone.
The solution is simple: don't use
subscribedirectly! It's a low level API. If you want it to behave as an Observable, write a function that turns it into an observable! It isn't hard to do:If you don't want to use RX and prefer a callback with previous and next value, again, easy to do. You can even do selecting as part of it:
We can provide either as a utility inside Redux, if we can converge on the API people want.
What we _can't_ do is make Store interface less low-level and duplicating functionality between methods, because then building extensions on top of it becomes way harder.