-
-
Notifications
You must be signed in to change notification settings - Fork 127
/
Copy pathlazy-observable.ts
95 lines (93 loc) · 3.04 KB
/
lazy-observable.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
import { observable, action, _allowStateChanges } from "mobx"
export interface ILazyObservable<T> {
current(): T
refresh(): T
reset(): T
pending: boolean
}
export function lazyObservable<T>(
fetch: (sink: (newValue: T) => void) => void
): ILazyObservable<T | undefined>
export function lazyObservable<T>(
fetch: (sink: (newValue: T) => void) => void,
initialValue: T
): ILazyObservable<T>
/**
* `lazyObservable` creates an observable around a `fetch` method that will not be invoked
* until the observable is needed the first time.
* The fetch method receives a `sink` callback which can be used to replace the
* current value of the lazyObservable. It is allowed to call `sink` multiple times
* to keep the lazyObservable up to date with some external resource.
*
* Note that it is the `current()` call itself which is being tracked by MobX,
* so make sure that you don't dereference to early.
*
* @example
* const userProfile = lazyObservable(
* sink => fetch("/myprofile").then(profile => sink(profile))
* )
*
* // use the userProfile in a React component:
* const Profile = observer(({ userProfile }) =>
* userProfile.current() === undefined
* ? <div>Loading user profile...</div>
* : <div>{userProfile.current().displayName}</div>
* )
*
* // triggers refresh the userProfile
* userProfile.refresh()
*
* @param {(sink: (newValue: T) => void) => void} fetch method that will be called the first time the value of this observable is accessed. The provided sink can be used to produce a new value, synchronously or asynchronously
* @param {T} [initialValue=undefined] optional initialValue that will be returned from `current` as long as the `sink` has not been called at least once
* @returns {{
* current(): T,
* refresh(): T,
* reset(): T
* pending: boolean
* }}
*/
export function lazyObservable<T>(
fetch: (sink: (newValue: T) => void) => void,
initialValue: T | undefined = undefined
): ILazyObservable<T | undefined> {
let started = false
const value = observable.box<T | undefined>(initialValue, { deep: false })
const pending = observable.box(false)
let currentFnc = () => {
if (!started) {
started = true
_allowStateChanges(true, () => {
pending.set(true)
})
fetch((newValue: T) => {
_allowStateChanges(true, () => {
value.set(newValue)
pending.set(false)
})
})
}
return value.get()
}
let resetFnc = action("lazyObservable-reset", () => {
started = false
value.set(initialValue)
return value.get()
})
return {
current: currentFnc,
refresh: () => {
if (started) {
started = false
return currentFnc()
} else {
return value.get()
}
},
reset: () => {
return resetFnc()
},
get pending() {
return pending.get()
},
}
}