@effect-rx/rx-react 0.38.1 → 0.38.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Hooks/package.json +6 -0
- package/ReactHydration/package.json +6 -0
- package/RegistryContext/package.json +6 -0
- package/dist/cjs/Hooks.js +254 -0
- package/dist/cjs/Hooks.js.map +1 -0
- package/dist/cjs/ReactHydration.js +50 -0
- package/dist/cjs/ReactHydration.js.map +1 -0
- package/dist/cjs/RegistryContext.js +73 -0
- package/dist/cjs/RegistryContext.js.map +1 -0
- package/dist/cjs/index.js +40 -293
- package/dist/cjs/index.js.map +1 -1
- package/dist/dts/Hooks.d.ts +79 -0
- package/dist/dts/Hooks.d.ts.map +1 -0
- package/dist/dts/ReactHydration.d.ts +11 -0
- package/dist/dts/ReactHydration.d.ts.map +1 -0
- package/dist/dts/RegistryContext.d.ts +25 -0
- package/dist/dts/RegistryContext.d.ts.map +1 -0
- package/dist/dts/index.d.ts +12 -98
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/esm/Hooks.js +215 -0
- package/dist/esm/Hooks.js.map +1 -0
- package/dist/esm/ReactHydration.js +23 -0
- package/dist/esm/ReactHydration.js.map +1 -0
- package/dist/esm/RegistryContext.js +45 -0
- package/dist/esm/RegistryContext.js.map +1 -0
- package/dist/esm/index.js +12 -243
- package/dist/esm/index.js.map +1 -1
- package/package.json +30 -2
- package/src/Hooks.ts +285 -0
- package/src/ReactHydration.ts +22 -0
- package/src/RegistryContext.ts +54 -0
- package/src/index.ts +12 -319
package/src/Hooks.ts
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since 1.0.0
|
|
3
|
+
*/
|
|
4
|
+
"use client"
|
|
5
|
+
import type * as Registry from "@effect-rx/rx/Registry"
|
|
6
|
+
import * as Result from "@effect-rx/rx/Result"
|
|
7
|
+
import * as Rx from "@effect-rx/rx/Rx"
|
|
8
|
+
import type * as RxRef from "@effect-rx/rx/RxRef"
|
|
9
|
+
import * as Cause from "effect/Cause"
|
|
10
|
+
import type * as Exit from "effect/Exit"
|
|
11
|
+
import { globalValue } from "effect/GlobalValue"
|
|
12
|
+
import * as React from "react"
|
|
13
|
+
import { RegistryContext } from "./RegistryContext.js"
|
|
14
|
+
|
|
15
|
+
interface RxStore<A> {
|
|
16
|
+
readonly subscribe: (f: () => void) => () => void
|
|
17
|
+
readonly snapshot: () => A
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const storeRegistry = globalValue(
|
|
21
|
+
"@effect-rx/rx-react/storeRegistry",
|
|
22
|
+
() => new WeakMap<Registry.Registry, WeakMap<Rx.Rx<any>, RxStore<any>>>()
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
function makeStore<A>(registry: Registry.Registry, rx: Rx.Rx<A>): RxStore<A> {
|
|
26
|
+
let stores = storeRegistry.get(registry)
|
|
27
|
+
if (stores === undefined) {
|
|
28
|
+
stores = new WeakMap()
|
|
29
|
+
storeRegistry.set(registry, stores)
|
|
30
|
+
}
|
|
31
|
+
const store = stores.get(rx)
|
|
32
|
+
if (store !== undefined) {
|
|
33
|
+
return store
|
|
34
|
+
}
|
|
35
|
+
const newStore: RxStore<A> = {
|
|
36
|
+
subscribe(f) {
|
|
37
|
+
return registry.subscribe(rx, f)
|
|
38
|
+
},
|
|
39
|
+
snapshot() {
|
|
40
|
+
return registry.get(rx)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
stores.set(rx, newStore)
|
|
44
|
+
return newStore
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function useStore<A>(registry: Registry.Registry, rx: Rx.Rx<A>): A {
|
|
48
|
+
const store = makeStore(registry, rx)
|
|
49
|
+
return React.useSyncExternalStore(store.subscribe, store.snapshot, store.snapshot)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const initialValuesSet = globalValue(
|
|
53
|
+
"@effect-rx/rx-react/initialValuesSet",
|
|
54
|
+
() => new WeakMap<Registry.Registry, WeakSet<Rx.Rx<any>>>()
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @since 1.0.0
|
|
59
|
+
* @category hooks
|
|
60
|
+
*/
|
|
61
|
+
export const useRxInitialValues = (initialValues: Iterable<readonly [Rx.Rx<any>, any]>): void => {
|
|
62
|
+
const registry = React.useContext(RegistryContext)
|
|
63
|
+
let set = initialValuesSet.get(registry)
|
|
64
|
+
if (set === undefined) {
|
|
65
|
+
set = new WeakSet()
|
|
66
|
+
initialValuesSet.set(registry, set)
|
|
67
|
+
}
|
|
68
|
+
for (const [rx, value] of initialValues) {
|
|
69
|
+
if (!set.has(rx)) {
|
|
70
|
+
set.add(rx)
|
|
71
|
+
;(registry as any).ensureNode(rx).setValue(value)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @since 1.0.0
|
|
78
|
+
* @category hooks
|
|
79
|
+
*/
|
|
80
|
+
export const useRxValue: {
|
|
81
|
+
<A>(rx: Rx.Rx<A>): A
|
|
82
|
+
<A, B>(rx: Rx.Rx<A>, f: (_: A) => B): B
|
|
83
|
+
} = <A>(rx: Rx.Rx<A>, f?: (_: A) => A): A => {
|
|
84
|
+
const registry = React.useContext(RegistryContext)
|
|
85
|
+
if (f) {
|
|
86
|
+
const rxB = React.useMemo(() => Rx.map(rx, f), [rx, f])
|
|
87
|
+
return useStore(registry, rxB)
|
|
88
|
+
}
|
|
89
|
+
return useStore(registry, rx)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function mountRx<A>(registry: Registry.Registry, rx: Rx.Rx<A>): void {
|
|
93
|
+
React.useEffect(() => registry.mount(rx), [rx, registry])
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function setRx<R, W>(registry: Registry.Registry, rx: Rx.Writable<R, W>): (_: W | ((_: R) => W)) => void {
|
|
97
|
+
return React.useCallback((value) => {
|
|
98
|
+
if (typeof value === "function") {
|
|
99
|
+
registry.set(rx, (value as any)(registry.get(rx)))
|
|
100
|
+
return
|
|
101
|
+
} else {
|
|
102
|
+
registry.set(rx, value)
|
|
103
|
+
}
|
|
104
|
+
}, [registry, rx])
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* @since 1.0.0
|
|
109
|
+
* @category hooks
|
|
110
|
+
*/
|
|
111
|
+
export const useRxMount = <A>(rx: Rx.Rx<A>): void => {
|
|
112
|
+
const registry = React.useContext(RegistryContext)
|
|
113
|
+
mountRx(registry, rx)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @since 1.0.0
|
|
118
|
+
* @category hooks
|
|
119
|
+
*/
|
|
120
|
+
export const useRxSet = <R, W>(rx: Rx.Writable<R, W>): (_: W | ((_: R) => W)) => void => {
|
|
121
|
+
const registry = React.useContext(RegistryContext)
|
|
122
|
+
mountRx(registry, rx)
|
|
123
|
+
return setRx(registry, rx)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* @since 1.0.0
|
|
128
|
+
* @category hooks
|
|
129
|
+
*/
|
|
130
|
+
export const useRxSetPromise = <E, A, W>(
|
|
131
|
+
rx: Rx.Writable<Result.Result<A, E>, W>
|
|
132
|
+
): (_: W) => Promise<Exit.Exit<A, E>> => {
|
|
133
|
+
const registry = React.useContext(RegistryContext)
|
|
134
|
+
const resolves = React.useMemo(() => new Set<(result: Exit.Exit<A, E>) => void>(), [])
|
|
135
|
+
React.useEffect(() =>
|
|
136
|
+
registry.subscribe(rx, (result) => {
|
|
137
|
+
if (result.waiting || result._tag === "Initial") return
|
|
138
|
+
const fns = Array.from(resolves)
|
|
139
|
+
resolves.clear()
|
|
140
|
+
const exit = Result.toExit(result)
|
|
141
|
+
fns.forEach((resolve) => resolve(exit as any))
|
|
142
|
+
}, { immediate: true }), [registry, rx, resolves])
|
|
143
|
+
return React.useCallback((value) =>
|
|
144
|
+
new Promise((resolve) => {
|
|
145
|
+
resolves.add(resolve)
|
|
146
|
+
registry.set(rx, value)
|
|
147
|
+
}), [registry, rx, resolves])
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* @since 1.0.0
|
|
152
|
+
* @category hooks
|
|
153
|
+
*/
|
|
154
|
+
export const useRxRefresh = <A>(rx: Rx.Rx<A>): () => void => {
|
|
155
|
+
const registry = React.useContext(RegistryContext)
|
|
156
|
+
mountRx(registry, rx)
|
|
157
|
+
return React.useCallback(() => {
|
|
158
|
+
registry.refresh(rx)
|
|
159
|
+
}, [registry, rx])
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* @since 1.0.0
|
|
164
|
+
* @category hooks
|
|
165
|
+
*/
|
|
166
|
+
export const useRx = <R, W>(
|
|
167
|
+
rx: Rx.Writable<R, W>
|
|
168
|
+
): readonly [value: R, setOrUpdate: (_: W | ((_: R) => W)) => void] => {
|
|
169
|
+
const registry = React.useContext(RegistryContext)
|
|
170
|
+
return [
|
|
171
|
+
useStore(registry, rx),
|
|
172
|
+
setRx(registry, rx)
|
|
173
|
+
] as const
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const rxPromiseMap = globalValue(
|
|
177
|
+
"@effect-rx/rx-react/rxPromiseMap",
|
|
178
|
+
() => ({
|
|
179
|
+
suspendOnWaiting: new Map<Rx.Rx<any>, Promise<void>>(),
|
|
180
|
+
default: new Map<Rx.Rx<any>, Promise<void>>()
|
|
181
|
+
})
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
function rxToPromise<A, E>(
|
|
185
|
+
registry: Registry.Registry,
|
|
186
|
+
rx: Rx.Rx<Result.Result<A, E>>,
|
|
187
|
+
suspendOnWaiting: boolean
|
|
188
|
+
) {
|
|
189
|
+
const map = suspendOnWaiting ? rxPromiseMap.suspendOnWaiting : rxPromiseMap.default
|
|
190
|
+
let promise = map.get(rx)
|
|
191
|
+
if (promise !== undefined) {
|
|
192
|
+
return promise
|
|
193
|
+
}
|
|
194
|
+
promise = new Promise<void>((resolve) => {
|
|
195
|
+
const dispose = registry.subscribe(rx, (result) => {
|
|
196
|
+
if (result._tag === "Initial" || (suspendOnWaiting && result.waiting)) {
|
|
197
|
+
return
|
|
198
|
+
}
|
|
199
|
+
setTimeout(dispose, 1000)
|
|
200
|
+
resolve()
|
|
201
|
+
map.delete(rx)
|
|
202
|
+
})
|
|
203
|
+
})
|
|
204
|
+
map.set(rx, promise)
|
|
205
|
+
return promise
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function rxResultOrSuspend<A, E>(
|
|
209
|
+
registry: Registry.Registry,
|
|
210
|
+
rx: Rx.Rx<Result.Result<A, E>>,
|
|
211
|
+
suspendOnWaiting: boolean
|
|
212
|
+
) {
|
|
213
|
+
const value = useStore(registry, rx)
|
|
214
|
+
if (value._tag === "Initial" || (suspendOnWaiting && value.waiting)) {
|
|
215
|
+
throw rxToPromise(registry, rx, suspendOnWaiting)
|
|
216
|
+
}
|
|
217
|
+
return value
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* @since 1.0.0
|
|
222
|
+
* @category hooks
|
|
223
|
+
*/
|
|
224
|
+
export const useRxSuspense = <A, E>(
|
|
225
|
+
rx: Rx.Rx<Result.Result<A, E>>,
|
|
226
|
+
options?: { readonly suspendOnWaiting?: boolean }
|
|
227
|
+
): Result.Success<A, E> | Result.Failure<A, E> => {
|
|
228
|
+
const registry = React.useContext(RegistryContext)
|
|
229
|
+
return rxResultOrSuspend(registry, rx, options?.suspendOnWaiting ?? false)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* @since 1.0.0
|
|
234
|
+
* @category hooks
|
|
235
|
+
*/
|
|
236
|
+
export const useRxSuspenseSuccess = <A, E>(
|
|
237
|
+
rx: Rx.Rx<Result.Result<A, E>>,
|
|
238
|
+
options?: { readonly suspendOnWaiting?: boolean }
|
|
239
|
+
): Result.Success<A, E> => {
|
|
240
|
+
const result = useRxSuspense(rx, options)
|
|
241
|
+
if (result._tag === "Failure") {
|
|
242
|
+
throw Cause.squash(result.cause)
|
|
243
|
+
}
|
|
244
|
+
return result
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* @since 1.0.0
|
|
249
|
+
* @category hooks
|
|
250
|
+
*/
|
|
251
|
+
export const useRxSubscribe = <A>(
|
|
252
|
+
rx: Rx.Rx<A>,
|
|
253
|
+
f: (_: A) => void,
|
|
254
|
+
options?: { readonly immediate?: boolean }
|
|
255
|
+
): void => {
|
|
256
|
+
const registry = React.useContext(RegistryContext)
|
|
257
|
+
React.useEffect(
|
|
258
|
+
() => registry.subscribe(rx, f, options),
|
|
259
|
+
[registry, rx, f, options?.immediate]
|
|
260
|
+
)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* @since 1.0.0
|
|
265
|
+
* @category hooks
|
|
266
|
+
*/
|
|
267
|
+
export const useRxRef = <A>(ref: RxRef.ReadonlyRef<A>): A => {
|
|
268
|
+
const [, setValue] = React.useState(ref.value)
|
|
269
|
+
React.useEffect(() => ref.subscribe(setValue), [ref])
|
|
270
|
+
return ref.value
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* @since 1.0.0
|
|
275
|
+
* @category hooks
|
|
276
|
+
*/
|
|
277
|
+
export const useRxRefProp = <A, K extends keyof A>(ref: RxRef.RxRef<A>, prop: K): RxRef.RxRef<A[K]> =>
|
|
278
|
+
React.useMemo(() => ref.prop(prop), [ref, prop])
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* @since 1.0.0
|
|
282
|
+
* @category hooks
|
|
283
|
+
*/
|
|
284
|
+
export const useRxRefPropValue = <A, K extends keyof A>(ref: RxRef.RxRef<A>, prop: K): A[K] =>
|
|
285
|
+
useRxRef(useRxRefProp(ref, prop))
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since 1.0.0
|
|
3
|
+
*/
|
|
4
|
+
"use client"
|
|
5
|
+
import * as Hydration from "@effect-rx/rx/Hydration"
|
|
6
|
+
import * as React from "react"
|
|
7
|
+
import { RegistryContext } from "./RegistryContext.js"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @since 1.0.0
|
|
11
|
+
* @category components
|
|
12
|
+
*/
|
|
13
|
+
export const HydrationBoundary: React.FC<{
|
|
14
|
+
state: Iterable<Hydration.DehydratedRx>
|
|
15
|
+
children?: React.ReactNode
|
|
16
|
+
}> = ({ children, state }) => {
|
|
17
|
+
const registry = React.useContext(RegistryContext)
|
|
18
|
+
React.useEffect(() => {
|
|
19
|
+
Hydration.hydrate(registry, state)
|
|
20
|
+
}, [registry, state])
|
|
21
|
+
return React.createElement(React.Fragment, {}, children)
|
|
22
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since 1.0.0
|
|
3
|
+
*/
|
|
4
|
+
"use client"
|
|
5
|
+
import * as Registry from "@effect-rx/rx/Registry"
|
|
6
|
+
import type * as Rx from "@effect-rx/rx/Rx"
|
|
7
|
+
import * as React from "react"
|
|
8
|
+
import * as Scheduler from "scheduler"
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @since 1.0.0
|
|
12
|
+
* @category context
|
|
13
|
+
*/
|
|
14
|
+
export function scheduleTask(f: () => void): void {
|
|
15
|
+
Scheduler.unstable_scheduleCallback(Scheduler.unstable_LowPriority, f)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @since 1.0.0
|
|
20
|
+
* @category context
|
|
21
|
+
*/
|
|
22
|
+
export const RegistryContext = React.createContext<Registry.Registry>(Registry.make({
|
|
23
|
+
scheduleTask,
|
|
24
|
+
defaultIdleTTL: 400
|
|
25
|
+
}))
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @since 1.0.0
|
|
29
|
+
* @category context
|
|
30
|
+
*/
|
|
31
|
+
export const RegistryProvider = (options: {
|
|
32
|
+
readonly children?: React.ReactNode | undefined
|
|
33
|
+
readonly initialValues?: Iterable<readonly [Rx.Rx<any>, any]> | undefined
|
|
34
|
+
readonly scheduleTask?: ((f: () => void) => void) | undefined
|
|
35
|
+
readonly timeoutResolution?: number | undefined
|
|
36
|
+
readonly defaultIdleTTL?: number | undefined
|
|
37
|
+
}) => {
|
|
38
|
+
const registry = React.useMemo(() =>
|
|
39
|
+
Registry.make({
|
|
40
|
+
scheduleTask,
|
|
41
|
+
defaultIdleTTL: 400,
|
|
42
|
+
...options
|
|
43
|
+
}), [])
|
|
44
|
+
React.useEffect(() => () => {
|
|
45
|
+
registry.dispose()
|
|
46
|
+
}, [registry])
|
|
47
|
+
return React.createElement(RegistryContext.Provider, {
|
|
48
|
+
value: Registry.make({
|
|
49
|
+
scheduleTask,
|
|
50
|
+
defaultIdleTTL: 400,
|
|
51
|
+
...options
|
|
52
|
+
})
|
|
53
|
+
}, options?.children)
|
|
54
|
+
}
|