1 | import { createAtom, _allowStateChanges } from "mobx";
|
2 | import { NOOP, invariant } from "./utils";
|
3 | /**
|
4 | * `fromResource` creates an observable whose current state can be inspected using `.current()`,
|
5 | * and which can be kept in sync with some external datasource that can be subscribed to.
|
6 | *
|
7 | * The created observable will only subscribe to the datasource if it is in use somewhere,
|
8 | * (un)subscribing when needed. To enable `fromResource` to do that two callbacks need to be provided,
|
9 | * one to subscribe, and one to unsubscribe. The subscribe callback itself will receive a `sink` callback, which can be used
|
10 | * to update the current state of the observable, allowing observes to react.
|
11 | *
|
12 | * Whatever is passed to `sink` will be returned by `current()`. The values passed to the sink will not be converted to
|
13 | * observables automatically, but feel free to do so.
|
14 | * It is the `current()` call itself which is being tracked,
|
15 | * so make sure that you don't dereference to early.
|
16 | *
|
17 | * For inspiration, an example integration with the apollo-client on [github](https://github.com/apollostack/apollo-client/issues/503#issuecomment-241101379),
|
18 | * or the [implementation](https://github.com/mobxjs/mobx-utils/blob/1d17cf7f7f5200937f68cc0b5e7ec7f3f71dccba/src/now.ts#L43-L57) of `mobxUtils.now`
|
19 | *
|
20 | * The following example code creates an observable that connects to a `dbUserRecord`,
|
21 | * which comes from an imaginary database and notifies when it has changed.
|
22 | *
|
23 | * @example
|
24 | * function createObservableUser(dbUserRecord) {
|
25 | * let currentSubscription;
|
26 | * return fromResource(
|
27 | * (sink) => {
|
28 | * // sink the current state
|
29 | * sink(dbUserRecord.fields)
|
30 | * // subscribe to the record, invoke the sink callback whenever new data arrives
|
31 | * currentSubscription = dbUserRecord.onUpdated(() => {
|
32 | * sink(dbUserRecord.fields)
|
33 | * })
|
34 | * },
|
35 | * () => {
|
36 | * // the user observable is not in use at the moment, unsubscribe (for now)
|
37 | * dbUserRecord.unsubscribe(currentSubscription)
|
38 | * }
|
39 | * )
|
40 | * }
|
41 | *
|
42 | * // usage:
|
43 | * const myUserObservable = createObservableUser(myDatabaseConnector.query("name = 'Michel'"))
|
44 | *
|
45 | * // use the observable in autorun
|
46 | * autorun(() => {
|
47 | * // printed everytime the database updates its records
|
48 | * console.log(myUserObservable.current().displayName)
|
49 | * })
|
50 | *
|
51 | * // ... or a component
|
52 | * const userComponent = observer(({ user }) =>
|
53 | * <div>{user.current().displayName}</div>
|
54 | * )
|
55 | *
|
56 | * @export
|
57 | * @template T
|
58 | * @param {(sink: (newValue: T) => void) => void} subscriber
|
59 | * @param {IDisposer} [unsubscriber=NOOP]
|
60 | * @param {T} [initialValue=undefined] the data that will be returned by `get()` until the `sink` has emitted its first data
|
61 | * @returns {{
|
62 | * current(): T;
|
63 | * dispose(): void;
|
64 | * isAlive(): boolean;
|
65 | * }}
|
66 | */
|
67 | export function fromResource(subscriber, unsubscriber, initialValue) {
|
68 | if (unsubscriber === void 0) { unsubscriber = NOOP; }
|
69 | if (initialValue === void 0) { initialValue = undefined; }
|
70 | var isActive = false;
|
71 | var isDisposed = false;
|
72 | var value = initialValue;
|
73 | var suspender = function () {
|
74 | if (isActive) {
|
75 | isActive = false;
|
76 | unsubscriber();
|
77 | }
|
78 | };
|
79 | var atom = createAtom("ResourceBasedObservable", function () {
|
80 | invariant(!isActive && !isDisposed);
|
81 | isActive = true;
|
82 | subscriber(function (newValue) {
|
83 | _allowStateChanges(true, function () {
|
84 | value = newValue;
|
85 | atom.reportChanged();
|
86 | });
|
87 | });
|
88 | }, suspender);
|
89 | return {
|
90 | current: function () {
|
91 | invariant(!isDisposed, "subscribingObservable has already been disposed");
|
92 | var isBeingTracked = atom.reportObserved();
|
93 | if (!isBeingTracked && !isActive)
|
94 | console.warn("Called `get` of a subscribingObservable outside a reaction. Current value will be returned but no new subscription has started");
|
95 | return value;
|
96 | },
|
97 | dispose: function () {
|
98 | isDisposed = true;
|
99 | suspender();
|
100 | },
|
101 | isAlive: function () { return isActive; },
|
102 | };
|
103 | }
|