Skip to content

Commit

Permalink
feat(state$): remove previously deprecated store.dispatch/getState() …
Browse files Browse the repository at this point in the history
…from epics (NOT from your UI code)

BREAKING CHANGE: previously the second argument to your epics was a "lite" version of the redux store with store.dispatch() and store.getState(), however this has been replaced with a stream of a state$ in v1.0.0 of redux-observable and the old methods were deprecated with warnings in previous alpha versions. This release removes them entirely. See https://redux-observable.js.org/MIGRATION.html
  • Loading branch information
jayphelps committed Jun 1, 2018
1 parent d0bca29 commit b352a98
Show file tree
Hide file tree
Showing 4 changed files with 20 additions and 79 deletions.
27 changes: 7 additions & 20 deletions src/StateObservable.js
Expand Up @@ -12,11 +12,16 @@ export const UNSET_STATE_VALUE = {};
// requires an initial value (which would be undefined)
// and if an epic subscribes to it on app boot it would
// synchronously emit that undefined value incorrectly.
// We also don't want to expose a Subject to the user at
// We could use ReplaySubject to get that behavior, but
// then it wouldn't have the .value property.
// We also don't want to expose any Subject to the user at
// all because then they could do `state$.next()` and
// screw things up and it would just be a footgun.
// This also allows us to add a warning message for
// accessing the state$.value property before redux
// has initialized.
export class StateObservable extends Observable {
constructor(stateSubject, store) {
constructor(stateSubject) {
super(subscriber => {
const subscription = this.__notifier.subscribe(subscriber);
if (this.__value !== UNSET_STATE_VALUE && subscription && !subscription.closed) {
Expand All @@ -25,12 +30,8 @@ export class StateObservable extends Observable {
return subscription;
});

// If you're reading this, keep in mind that this is
// NOT part of the public API and will be removed!
this.__store = store;
this.__value = UNSET_STATE_VALUE;
this.__notifier = new Subject();

this.__subscription = stateSubject.subscribe(value => {
// We only want to update state$ if it has actually changed since
// redux requires reducers use immutability patterns.
Expand All @@ -53,18 +54,4 @@ export class StateObservable extends Observable {
return this.__value;
}
}

getState() {
if (process.env.NODE_ENV !== 'production') {
require('./utils/console').deprecate('calling store.getState() in your Epics is deprecated and will be removed. The second argument to your Epic is now a stream of state$ (a StateObservable), instead of the store. To imperatively get the current state use state$.value instead of getState(). Alternatively, since it\'s now a stream you can compose and react to state changes.\n\n function <T, R, S, D>(action$: ActionsObservable<T>, state$: StateObservable<S>, dependencies?: D): Observable<R>\n\nLearn more: https://redux-observable.js.org/MIGRATION.html');
}
return this.__value;
}

dispatch(...args) {
if (process.env.NODE_ENV !== 'production') {
require('./utils/console').deprecate('calling store.dispatch() directly in your Epics is deprecated and will be removed. The second argument to your Epic is now a stream of state$ (a StateObservable), instead of the store. Instead of calling store.dispatch() in your Epic, emit actions through the Observable your Epic returns.\n\n function <T, R, S, D>(action$: ActionsObservable<T>, state$: StateObservable<S>, dependencies?: D): Observable<R>\n\nLearn more: https://redux-observable.js.org/MIGRATION.html');
}
return this.__store.dispatch(...args);
}
}
8 changes: 4 additions & 4 deletions src/createEpicMiddleware.js
Expand Up @@ -24,9 +24,13 @@ export function createEpicMiddleware(options = defaultOptions) {
const actionSubject$ = new Subject().pipe(
observeOn(queueScheduler)
);
const stateSubject$ = new Subject().pipe(
observeOn(queueScheduler)
);
const action$ = options.adapter.input(
new ActionsObservable(actionSubject$)
);
const state$ = new StateObservable(stateSubject$);
const epic$ = new Subject();
let store;

Expand All @@ -36,10 +40,6 @@ export function createEpicMiddleware(options = defaultOptions) {
require('./utils/console').warn('this middleware is already associated with a store. createEpicMiddleware should be called for every store.\n\nLearn more: https://goo.gl/2GQ7Da');
}
store = _store;
const stateSubject$ = new Subject().pipe(
observeOn(queueScheduler)
);
const state$ = new StateObservable(stateSubject$, _store);

const result$ = epic$.pipe(
map(epic => {
Expand Down
46 changes: 0 additions & 46 deletions test/createEpicMiddleware-spec.js
Expand Up @@ -60,25 +60,6 @@ describe('createEpicMiddleware', () => {
expect(console.warn.getCall(0).args[0]).to.equal('redux-observable | WARNING: this middleware is already associated with a store. createEpicMiddleware should be called for every store.\n\nLearn more: https://goo.gl/2GQ7Da');
});

it('should warn about improper use of dispatch function', () => {
spySandbox.spy(console, 'warn');
const reducer = (state = [], action) => state.concat(action);
const epic = (action$, store) => action$.pipe(
ofType('PING'),
map(() => store.dispatch({ type: 'PONG' })),
ignoreElements()
);

const middleware = createEpicMiddleware();
const store = createStore(reducer, applyMiddleware(middleware));
middleware.run(epic);

store.dispatch({ type: 'PING' });

expect(console.warn.callCount).to.equal(1);
expect(console.warn.getCall(0).args[0]).to.equal('redux-observable | DEPRECATION: calling store.dispatch() directly in your Epics is deprecated and will be removed. The second argument to your Epic is now a stream of state$ (a StateObservable), instead of the store. Instead of calling store.dispatch() in your Epic, emit actions through the Observable your Epic returns.\n\n function <T, R, S, D>(action$: ActionsObservable<T>, state$: StateObservable<S>, dependencies?: D): Observable<R>\n\nLearn more: https://redux-observable.js.org/MIGRATION.html');
});

it('should update state$ after an action goes through reducers but before epics', () => {
const actions = [];
const reducer = (state = 0, action) => {
Expand Down Expand Up @@ -220,33 +201,6 @@ describe('createEpicMiddleware', () => {
}]);
});

it('should warn about deprecated use of store.getState()', () => {
spySandbox.spy(console, 'warn');
const actions = [];
const reducer = (state = { foo: 'bar' }, action) => {
actions.push(action);
return state;
};
const epic = (action$, state$) =>
action$.pipe(
ofType('PING'),
map(() => ({
type: 'RESULT',
state: state$.getState()
}))
);

const middleware = createEpicMiddleware();
const store = createStore(reducer, applyMiddleware(middleware));
middleware.run(epic);

store.dispatch({ type: 'PING' });

expect(console.warn.callCount).to.equal(1);
expect(console.warn.getCall(0).args[0]).to.equal('redux-observable | DEPRECATION: calling store.getState() in your Epics is deprecated and will be removed. The second argument to your Epic is now a stream of state$ (a StateObservable), instead of the store. To imperatively get the current state use state$.value instead of getState(). Alternatively, since it\'s now a stream you can compose and react to state changes.\n\n function <T, R, S, D>(action$: ActionsObservable<T>, state$: StateObservable<S>, dependencies?: D): Observable<R>\n\nLearn more: https://redux-observable.js.org/MIGRATION.html');
expect(actions[actions.length - 1].state).to.equal(store.getState());
});

it('should accept an epic that wires up action$ input to action$ out', () => {
const reducer = (state = [], action) => state.concat(action);
const epic = (action$, state$) =>
Expand Down
18 changes: 9 additions & 9 deletions test/typings.ts
Expand Up @@ -23,12 +23,12 @@ interface Dependencies {
func(value: string): string;
}

const epic1: Epic<FluxStandardAction, FluxStandardAction, State> = (action$, store$) =>
const epic1: Epic<FluxStandardAction, FluxStandardAction, State> = (action$, state$) =>
action$.pipe(
ofType('FIRST'),
map(() => ({
type: 'first',
payload: store$.getState()
payload: state$.value
}))
);

Expand Down Expand Up @@ -71,7 +71,7 @@ const epic6: Epic<FluxStandardAction> = action$ =>
}))
);

const epic7: Epic<FluxStandardAction, FluxStandardAction, State, Dependencies> = (action$, _, dependencies) =>
const epic7: Epic<FluxStandardAction, FluxStandardAction, State, Dependencies> = (action$, state$, dependencies) =>
action$.pipe(
ofType('SEVENTH'),
map(({ type, payload }) => ({
Expand All @@ -80,7 +80,7 @@ const epic7: Epic<FluxStandardAction, FluxStandardAction, State, Dependencies> =
}))
);

const epic8: Epic<FluxStandardAction, FluxStandardAction, State, Dependencies> = (action$, store, dependencies) =>
const epic8: Epic<FluxStandardAction, FluxStandardAction, State, Dependencies> = (action$, state$, dependencies) =>
action$.pipe(
ofType('EIGHTH'),
map(({ type, payload }) => ({
Expand All @@ -98,7 +98,7 @@ interface Epic9_Output {
payload: string,
}

const epic9_1: Epic<FluxStandardAction, Epic9_Output, State, Dependencies> = (action$, store, dependencies) =>
const epic9_1: Epic<FluxStandardAction, Epic9_Output, State, Dependencies> = (action$, state$, dependencies) =>
action$.pipe(
ofType<FluxStandardAction, Epic9_Input>('NINTH'),
map(({ type, payload }) => ({
Expand All @@ -107,7 +107,7 @@ const epic9_1: Epic<FluxStandardAction, Epic9_Output, State, Dependencies> = (ac
}))
);

const epic9_2 = (action$: ActionsObservable<FluxStandardAction>, store: StateObservable<void>, dependencies: Dependencies) =>
const epic9_2 = (action$: ActionsObservable<FluxStandardAction>, state$: StateObservable<void>, dependencies: Dependencies) =>
action$.pipe(
ofType<FluxStandardAction, Epic9_Input>('NINTH'),
map(({ type, payload }) => ({
Expand All @@ -126,10 +126,10 @@ const epicMiddleware1: EpicMiddleware<FluxStandardAction> = createEpicMiddleware
const epicMiddleware2 = createEpicMiddleware({ dependencies });

interface CustomEpic<T extends Action, S, U> {
(action$: ActionsObservable<T>, store: StateObservable<S>, api: U): Observable<T>;
(action$: ActionsObservable<T>, state$: StateObservable<S>, api: U): Observable<T>;
}

const customEpic: CustomEpic<FluxStandardAction, State, number> = (action$, store, some) =>
const customEpic: CustomEpic<FluxStandardAction, State, number> = (action$, state$, some) =>
action$.pipe(
ofType('CUSTOM1'),
map(({ type, payload }) => ({
Expand All @@ -138,7 +138,7 @@ const customEpic: CustomEpic<FluxStandardAction, State, number> = (action$, stor
}))
);

const customEpic2: CustomEpic<FluxStandardAction, State, number> = (action$, store, some) =>
const customEpic2: CustomEpic<FluxStandardAction, State, number> = (action$, state$, some) =>
action$.pipe(
ofType('CUSTOM2'),
map(({ type, payload }) => ({
Expand Down

0 comments on commit b352a98

Please sign in to comment.