1/**
2 * React context and provider for the Atom registry used by Effect Atom hooks.
3 * The registry stores atom values, schedules update work, and cleans up unused
4 * atoms. Sharing one registry through React context lets components in the same
5 * subtree read and write the same atom state.
6 *
7 * @since 4.0.0
8 */
9"use client"
10
11import type * as Atom from "effect/reactivity/Atom"
12import * as AtomRegistry from "effect/reactivity/AtomRegistry"
13import * as React from "react"
14import * as Scheduler from "scheduler"
15
16/**
17 * Schedules Atom registry work with React's scheduler at low priority and
18 * returns a cancellation function for the scheduled task.
19 *
20 * @stability unstable
21 * @category context
22 * @since 4.0.0
23 */
24export function scheduleTask(f: () => void): () => void {
25 const node = Scheduler.unstable_scheduleCallback(Scheduler.unstable_LowPriority, f)
26 return () => Scheduler.unstable_cancelCallback(node)
27}
28
29/**
30 * Provides a React context that supplies the `AtomRegistry` used by Atom hooks and
31 * hydration helpers, defaulting to a standalone registry when no provider is
32 * present.
33 *
34 * **When to use**
35 *
36 * Use to supply an existing `AtomRegistry` through React context when hooks or
37 * hydration helpers need to share registry state that is managed outside
38 * `RegistryProvider`.
39 *
40 * @see {@link RegistryProvider} for creating and providing a registry for a React subtree
41 *
42 * @category context
43 * @since 4.0.0
44 */
45export const RegistryContext = React.createContext<AtomRegistry.AtomRegistry>(AtomRegistry.make({
46 scheduleTask,
47 defaultIdleTTL: 400
48}))
49
50/**
51 * Provides a stable `AtomRegistry` to a React subtree, optionally seeding
52 * initial atom values and overriding registry scheduling or idle settings.
53 *
54 * **When to use**
55 *
56 * Use to scope atom state, scheduling, and idle cleanup to a React subtree.
57 *
58 * **Details**
59 *
60 * The provider creates one `AtomRegistry` with `AtomRegistry.make`, passes it
61 * through `RegistryContext.Provider`, and forwards `initialValues`,
62 * `scheduleTask`, `timeoutResolution`, and `defaultIdleTTL` only when that
63 * registry is created.
64 *
65 * **Gotchas**
66 *
67 * Option changes after the first render do not rebuild the registry. When the
68 * provider unmounts, registry disposal is delayed briefly and canceled if the
69 * provider remounts before the timeout fires.
70 *
71 * @see {@link RegistryContext} for the React context supplied by this provider
72 *
73 * @category context
74 * @since 4.0.0
75 */
76export const RegistryProvider = (options: {
77 readonly children?: React.ReactNode | undefined
78 readonly initialValues?: Iterable<readonly [Atom.Atom<any>, any]> | undefined
79 readonly scheduleTask?: ((f: () => void) => () => void) | undefined
80 readonly timeoutResolution?: number | undefined
81 readonly defaultIdleTTL?: number | undefined
82}) => {
83 const ref = React.useRef<{
84 readonly registry: AtomRegistry.AtomRegistry
85 timeout?: number | undefined
86 }>(null)
87 if (ref.current === null) {
88 ref.current = {
89 registry: AtomRegistry.make({
90 scheduleTask: options.scheduleTask ?? scheduleTask,
91 initialValues: options.initialValues,
92 timeoutResolution: options.timeoutResolution,
93 defaultIdleTTL: options.defaultIdleTTL
94 })
95 }
96 }
97 React.useEffect(() => {
98 if (ref.current?.timeout !== undefined) {
99 clearTimeout(ref.current.timeout)
100 }
101 return () => {
102 ref.current!.timeout = setTimeout(() => {
103 ref.current?.registry.dispose()
104 ref.current = null
105 }, 500) as any
106 }
107 }, [ref])
108 return React.createElement(RegistryContext.Provider, { value: ref.current.registry }, options?.children)
109}