Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(state$): state$ is now an Observable not a Subject
- Loading branch information
Showing
8 changed files
with
168 additions
and
97 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import { Observable } from 'rxjs'; | ||
|
||
// Used as a placeholder value so we can tell | ||
// whether or not the real state has been set, | ||
// even if the real state was `undefined` | ||
export const UNSET_STATE_VALUE = {}; | ||
|
||
// We can't use BehaviorSubject because when we create | ||
// and give state$ to your epics your reducers have not | ||
// yet run, so there is no initial state yet until the | ||
// `@@redux/INIT` action is emitted. BehaviorSubject | ||
// 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 | ||
// all because then they could do `state$.next()` and | ||
// screw things up and it would just be a footgun. | ||
export class StateObservable extends Observable { | ||
constructor(stateSubject, store) { | ||
super(); | ||
this.source = stateSubject; | ||
// 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.source.subscribe(value => { | ||
this.__value = value; | ||
}); | ||
} | ||
|
||
_subscribe(subscriber) { | ||
const subscription = super._subscribe(subscriber); | ||
if (this.__value !== UNSET_STATE_VALUE && subscription && !subscription.closed) { | ||
subscriber.next(this.__value); | ||
} | ||
return subscription; | ||
} | ||
|
||
get value() { | ||
if (this.__value === UNSET_STATE_VALUE) { | ||
if (process.env.NODE_ENV !== 'production') { | ||
require('./utils/console').warn('You accessed state$.value inside one of your Epics, before your reducers have run for the first time, so there is no state yet. You\'ll need to wait until after the first action (@@redux/INIT) is dispatched or by using state$ as an Observable.'); | ||
} | ||
return undefined; | ||
} else { | ||
return this.__value; | ||
} | ||
} | ||
|
||
lift(operator) { | ||
const observable = new StateObservable(this); | ||
observable.operator = operator; | ||
return observable; | ||
} | ||
|
||
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); | ||
|
||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
export { createEpicMiddleware } from './createEpicMiddleware'; | ||
export { ActionsObservable } from './ActionsObservable'; | ||
export { StateSubject } from './StateSubject'; | ||
export { StateObservable } from './StateObservable'; | ||
export { combineEpics } from './combineEpics'; | ||
export { EPIC_END } from './EPIC_END'; | ||
export { ofType } from './operators'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,15 @@ | ||
const deprecationsSeen = {}; | ||
|
||
export const warn = (typeof console === 'object' && typeof console.warn === 'function') | ||
? msg => console.warn(msg) | ||
: () => {}; | ||
const consoleWarn = (typeof console === 'object' && typeof console.warn === 'function') | ||
? (...args) => console.warn(...args) | ||
: () => { }; | ||
|
||
export const deprecate = msg => { | ||
if (!deprecationsSeen[msg]) { | ||
deprecationsSeen[msg] = true; | ||
warn(`redux-observable | DEPRECATION: ${msg}`); | ||
consoleWarn(`redux-observable | DEPRECATION: ${msg}`); | ||
} | ||
}; | ||
|
||
export const warn = msg => { | ||
consoleWarn(`redux-observable | WARNING: ${msg}`); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
/* globals describe it */ | ||
import { expect } from 'chai'; | ||
import { StateObservable } from '../'; | ||
import { Observable, Subject } from 'rxjs'; | ||
|
||
describe('StateObservable', () => { | ||
it('should exist', () => { | ||
expect(StateObservable.prototype).to.be.instanceof(Observable); | ||
}); | ||
|
||
it('should mirror the source subject', () => { | ||
const input$ = new Subject(); | ||
const state$ = new StateObservable(input$); | ||
let result = null; | ||
|
||
state$.subscribe(state => { | ||
result = state; | ||
}); | ||
|
||
expect(result).to.equal(null); | ||
input$.next('first'); | ||
expect(result).to.equal('first'); | ||
input$.next('second'); | ||
expect(result).to.equal('second'); | ||
}); | ||
|
||
it('should cache last state on the `value` property', () => { | ||
const input$ = new Subject(); | ||
const state$ = new StateObservable(input$); | ||
|
||
expect(state$.value).to.equal(undefined); | ||
input$.next('first'); | ||
expect(state$.value).to.equal('first'); | ||
input$.next('second'); | ||
expect(state$.value).to.equal('second'); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters