atomirx 0.0.1 → 0.0.4
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/README.md +867 -160
- package/dist/core/atom.d.ts +83 -6
- package/dist/core/batch.d.ts +3 -3
- package/dist/core/derived.d.ts +55 -21
- package/dist/core/effect.d.ts +47 -51
- package/dist/core/getAtomState.d.ts +29 -0
- package/dist/core/promiseCache.d.ts +23 -32
- package/dist/core/select.d.ts +208 -29
- package/dist/core/types.d.ts +55 -19
- package/dist/core/withReady.d.ts +69 -0
- package/dist/index-CqO6BDwj.cjs +1 -0
- package/dist/index-D8RDOTB_.js +1319 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +9 -7
- package/dist/index.js +12 -10
- package/dist/react/index.cjs +10 -10
- package/dist/react/index.d.ts +2 -1
- package/dist/react/index.js +423 -379
- package/dist/react/rx.d.ts +114 -25
- package/dist/react/useAction.d.ts +5 -4
- package/dist/react/{useValue.d.ts → useSelector.d.ts} +56 -25
- package/dist/react/useSelector.test.d.ts +1 -0
- package/package.json +17 -1
- package/src/core/atom.test.ts +307 -43
- package/src/core/atom.ts +143 -21
- package/src/core/batch.test.ts +10 -10
- package/src/core/batch.ts +3 -3
- package/src/core/derived.test.ts +727 -72
- package/src/core/derived.ts +141 -73
- package/src/core/effect.test.ts +259 -39
- package/src/core/effect.ts +62 -85
- package/src/core/getAtomState.ts +69 -0
- package/src/core/promiseCache.test.ts +5 -3
- package/src/core/promiseCache.ts +76 -71
- package/src/core/select.ts +405 -130
- package/src/core/selector.test.ts +574 -32
- package/src/core/types.ts +54 -26
- package/src/core/withReady.test.ts +360 -0
- package/src/core/withReady.ts +127 -0
- package/src/core/withUse.ts +1 -1
- package/src/index.test.ts +4 -4
- package/src/index.ts +11 -6
- package/src/react/index.ts +2 -1
- package/src/react/rx.test.tsx +173 -18
- package/src/react/rx.tsx +274 -43
- package/src/react/useAction.test.ts +12 -14
- package/src/react/useAction.ts +11 -9
- package/src/react/{useValue.test.ts → useSelector.test.ts} +16 -16
- package/src/react/{useValue.ts → useSelector.ts} +64 -33
- package/v2.md +44 -44
- package/dist/index-2ok7ilik.js +0 -1217
- package/dist/index-B_5SFzfl.cjs +0 -1
- /package/dist/{react/useValue.test.d.ts → core/withReady.test.d.ts} +0 -0
package/dist/react/rx.d.ts
CHANGED
|
@@ -1,9 +1,45 @@
|
|
|
1
|
+
import { ReactElement, ReactNode } from 'react';
|
|
1
2
|
import { Atom, Equality } from '../core/types';
|
|
2
|
-
import {
|
|
3
|
+
import { ReactiveSelector } from '../core/select';
|
|
4
|
+
/**
|
|
5
|
+
* Options for rx() with inline loading/error handling and memoization control.
|
|
6
|
+
*/
|
|
7
|
+
export interface RxOptions<T> {
|
|
8
|
+
/** Equality function for value comparison */
|
|
9
|
+
equals?: Equality<T>;
|
|
10
|
+
/** Render function for loading state */
|
|
11
|
+
loading?: () => ReactNode;
|
|
12
|
+
/** Render function for error state */
|
|
13
|
+
error?: (props: {
|
|
14
|
+
error: unknown;
|
|
15
|
+
}) => ReactNode;
|
|
16
|
+
/**
|
|
17
|
+
* Dependencies array for selector memoization.
|
|
18
|
+
*
|
|
19
|
+
* Controls when the selector callback is recreated:
|
|
20
|
+
* - **Atom shorthand** (`rx(atom$)`): Always memoized by atom reference (deps ignored)
|
|
21
|
+
* - **Function selector without deps**: No memoization (recreated every render)
|
|
22
|
+
* - **Function selector with `deps: []`**: Stable forever (never recreated)
|
|
23
|
+
* - **Function selector with `deps: [a, b]`**: Recreated when deps change
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```tsx
|
|
27
|
+
* // No memoization (default for functions) - selector recreated every render
|
|
28
|
+
* rx(({ read }) => read(count$) * 2)
|
|
29
|
+
*
|
|
30
|
+
* // Stable selector - never recreated
|
|
31
|
+
* rx(({ read }) => read(count$) * 2, { deps: [] })
|
|
32
|
+
*
|
|
33
|
+
* // Recreate when multiplier changes
|
|
34
|
+
* rx(({ read }) => read(count$) * multiplier, { deps: [multiplier] })
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
deps?: unknown[];
|
|
38
|
+
}
|
|
3
39
|
/**
|
|
4
40
|
* Reactive inline component that renders atom values directly in JSX.
|
|
5
41
|
*
|
|
6
|
-
* `rx` is a convenience wrapper around `
|
|
42
|
+
* `rx` is a convenience wrapper around `useSelector` that returns a memoized
|
|
7
43
|
* React component instead of a value. This enables fine-grained reactivity
|
|
8
44
|
* without creating separate components for each reactive value.
|
|
9
45
|
*
|
|
@@ -13,25 +49,54 @@ import { ContextSelectorFn } from '../core/select';
|
|
|
13
49
|
*
|
|
14
50
|
* ```tsx
|
|
15
51
|
* // ❌ WRONG - Don't use async function
|
|
16
|
-
* rx(async ({
|
|
52
|
+
* rx(async ({ read }) => {
|
|
17
53
|
* const data = await fetch('/api');
|
|
18
54
|
* return data.name;
|
|
19
55
|
* });
|
|
20
56
|
*
|
|
21
57
|
* // ❌ WRONG - Don't return a Promise
|
|
22
|
-
* rx(({
|
|
58
|
+
* rx(({ read }) => fetch('/api').then(r => r.json()));
|
|
23
59
|
*
|
|
24
|
-
* // ✅ CORRECT - Create async atom and read with
|
|
60
|
+
* // ✅ CORRECT - Create async atom and read with read()
|
|
25
61
|
* const data$ = atom(fetch('/api').then(r => r.json()));
|
|
26
|
-
* rx(({
|
|
62
|
+
* rx(({ read }) => read(data$).name); // Suspends until resolved
|
|
27
63
|
* ```
|
|
28
64
|
*
|
|
65
|
+
* ## IMPORTANT: Do NOT Use try/catch - Use safe() Instead
|
|
66
|
+
*
|
|
67
|
+
* **Never wrap `read()` calls in try/catch blocks.** The `read()` function throws
|
|
68
|
+
* Promises when atoms are loading (Suspense pattern). A try/catch will catch
|
|
69
|
+
* these Promises and break the Suspense mechanism.
|
|
70
|
+
*
|
|
71
|
+
* ```tsx
|
|
72
|
+
* // ❌ WRONG - Catches Suspense Promise, breaks loading state
|
|
73
|
+
* rx(({ read }) => {
|
|
74
|
+
* try {
|
|
75
|
+
* return <span>{read(user$).name}</span>;
|
|
76
|
+
* } catch (e) {
|
|
77
|
+
* return <span>Error</span>; // Catches BOTH errors AND loading promises!
|
|
78
|
+
* }
|
|
79
|
+
* });
|
|
80
|
+
*
|
|
81
|
+
* // ✅ CORRECT - Use safe() to catch errors but preserve Suspense
|
|
82
|
+
* rx(({ read, safe }) => {
|
|
83
|
+
* const [err, user] = safe(() => read(user$));
|
|
84
|
+
* if (err) return <span>Error: {err.message}</span>;
|
|
85
|
+
* return <span>{user.name}</span>;
|
|
86
|
+
* });
|
|
87
|
+
* ```
|
|
88
|
+
*
|
|
89
|
+
* The `safe()` utility:
|
|
90
|
+
* - **Catches errors** and returns `[error, undefined]`
|
|
91
|
+
* - **Re-throws Promises** to preserve Suspense behavior
|
|
92
|
+
* - Returns `[undefined, result]` on success
|
|
93
|
+
*
|
|
29
94
|
* ## Why Use `rx`?
|
|
30
95
|
*
|
|
31
96
|
* Without `rx`, you need a separate component to subscribe to an atom:
|
|
32
97
|
* ```tsx
|
|
33
98
|
* function PostsList() {
|
|
34
|
-
* const posts =
|
|
99
|
+
* const posts = useSelector(postsAtom);
|
|
35
100
|
* return posts.map((post) => <Post post={post} />);
|
|
36
101
|
* }
|
|
37
102
|
*
|
|
@@ -49,8 +114,8 @@ import { ContextSelectorFn } from '../core/select';
|
|
|
49
114
|
* function Page() {
|
|
50
115
|
* return (
|
|
51
116
|
* <Suspense fallback={<Loading />}>
|
|
52
|
-
* {rx(({
|
|
53
|
-
*
|
|
117
|
+
* {rx(({ read }) =>
|
|
118
|
+
* read(postsAtom).map((post) => <Post post={post} />)
|
|
54
119
|
* )}
|
|
55
120
|
* </Suspense>
|
|
56
121
|
* );
|
|
@@ -68,7 +133,7 @@ import { ContextSelectorFn } from '../core/select';
|
|
|
68
133
|
*
|
|
69
134
|
* ## Async Atoms (Suspense-Style API)
|
|
70
135
|
*
|
|
71
|
-
* `rx` inherits the Suspense-style API from `
|
|
136
|
+
* `rx` inherits the Suspense-style API from `useSelector`:
|
|
72
137
|
* - **Loading state**: The getter throws a Promise (triggers Suspense)
|
|
73
138
|
* - **Error state**: The getter throws the error (triggers ErrorBoundary)
|
|
74
139
|
* - **Resolved state**: The getter returns the value
|
|
@@ -79,7 +144,7 @@ import { ContextSelectorFn } from '../core/select';
|
|
|
79
144
|
* return (
|
|
80
145
|
* <ErrorBoundary fallback={<div>Error!</div>}>
|
|
81
146
|
* <Suspense fallback={<div>Loading...</div>}>
|
|
82
|
-
* {rx(({
|
|
147
|
+
* {rx(({ read }) => read(userAtom).name)}
|
|
83
148
|
* </Suspense>
|
|
84
149
|
* </ErrorBoundary>
|
|
85
150
|
* );
|
|
@@ -88,9 +153,9 @@ import { ContextSelectorFn } from '../core/select';
|
|
|
88
153
|
*
|
|
89
154
|
* Or catch errors in the selector to handle loading/error inline:
|
|
90
155
|
* ```tsx
|
|
91
|
-
* {rx(({
|
|
156
|
+
* {rx(({ read }) => {
|
|
92
157
|
* try {
|
|
93
|
-
* return
|
|
158
|
+
* return read(userAtom).name;
|
|
94
159
|
* } catch {
|
|
95
160
|
* return "Loading...";
|
|
96
161
|
* }
|
|
@@ -98,13 +163,37 @@ import { ContextSelectorFn } from '../core/select';
|
|
|
98
163
|
* ```
|
|
99
164
|
*
|
|
100
165
|
* @template T - The type of the selected/derived value
|
|
101
|
-
* @param selector - Context-based selector function with `{
|
|
166
|
+
* @param selector - Context-based selector function with `{ read, all, any, race, settled }`.
|
|
102
167
|
* Must return sync value, not a Promise.
|
|
103
168
|
* @param equals - Equality function or shorthand ("strict", "shallow", "deep").
|
|
104
169
|
* Defaults to "shallow".
|
|
105
170
|
* @returns A React element that renders the selected value
|
|
106
171
|
* @throws Error if selector returns a Promise or PromiseLike
|
|
107
172
|
*
|
|
173
|
+
* ## IMPORTANT: Atom Value Must Be ReactNode
|
|
174
|
+
*
|
|
175
|
+
* When using the shorthand `rx(atom)`, the atom's value must be a valid `ReactNode`
|
|
176
|
+
* (string, number, boolean, null, undefined, or React element). Objects and arrays
|
|
177
|
+
* are NOT valid ReactNode values and will cause React to throw an error.
|
|
178
|
+
*
|
|
179
|
+
* ```tsx
|
|
180
|
+
* // ✅ CORRECT - Atom contains ReactNode (number)
|
|
181
|
+
* const count$ = atom(5);
|
|
182
|
+
* rx(count$);
|
|
183
|
+
*
|
|
184
|
+
* // ✅ CORRECT - Atom contains ReactNode (string)
|
|
185
|
+
* const name$ = atom("John");
|
|
186
|
+
* rx(name$);
|
|
187
|
+
*
|
|
188
|
+
* // ❌ WRONG - Atom contains object (not ReactNode)
|
|
189
|
+
* const user$ = atom({ name: "John", age: 30 });
|
|
190
|
+
* rx(user$); // React error: "Objects are not valid as a React child"
|
|
191
|
+
*
|
|
192
|
+
* // ✅ CORRECT - Use selector to extract ReactNode from object
|
|
193
|
+
* rx(({ read }) => read(user$).name);
|
|
194
|
+
* rx(({ read }) => <UserCard user={read(user$)} />);
|
|
195
|
+
* ```
|
|
196
|
+
*
|
|
108
197
|
* @example Shorthand - render atom value directly
|
|
109
198
|
* ```tsx
|
|
110
199
|
* const count = atom(5);
|
|
@@ -119,7 +208,7 @@ import { ContextSelectorFn } from '../core/select';
|
|
|
119
208
|
* const count = atom(5);
|
|
120
209
|
*
|
|
121
210
|
* function DoubledCounter() {
|
|
122
|
-
* return <div>Doubled: {rx(({
|
|
211
|
+
* return <div>Doubled: {rx(({ read }) => read(count) * 2)}</div>;
|
|
123
212
|
* }
|
|
124
213
|
* ```
|
|
125
214
|
*
|
|
@@ -131,7 +220,7 @@ import { ContextSelectorFn } from '../core/select';
|
|
|
131
220
|
* function FullName() {
|
|
132
221
|
* return (
|
|
133
222
|
* <div>
|
|
134
|
-
* {rx(({
|
|
223
|
+
* {rx(({ read }) => `${read(firstName)} ${read(lastName)}`)}
|
|
135
224
|
* </div>
|
|
136
225
|
* );
|
|
137
226
|
* }
|
|
@@ -158,14 +247,14 @@ import { ContextSelectorFn } from '../core/select';
|
|
|
158
247
|
* return (
|
|
159
248
|
* <div>
|
|
160
249
|
* <header>
|
|
161
|
-
* <Suspense fallback="...">{rx(({
|
|
250
|
+
* <Suspense fallback="...">{rx(({ read }) => read(userAtom).name)}</Suspense>
|
|
162
251
|
* </header>
|
|
163
252
|
* <main>
|
|
164
253
|
* <Suspense fallback="...">
|
|
165
|
-
* {rx(({
|
|
254
|
+
* {rx(({ read }) => read(postsAtom).length)} posts
|
|
166
255
|
* </Suspense>
|
|
167
256
|
* <Suspense fallback="...">
|
|
168
|
-
* {rx(({
|
|
257
|
+
* {rx(({ read }) => read(notificationsAtom).length)} notifications
|
|
169
258
|
* </Suspense>
|
|
170
259
|
* </main>
|
|
171
260
|
* </div>
|
|
@@ -182,8 +271,8 @@ import { ContextSelectorFn } from '../core/select';
|
|
|
182
271
|
* function Info() {
|
|
183
272
|
* return (
|
|
184
273
|
* <div>
|
|
185
|
-
* {rx(({
|
|
186
|
-
*
|
|
274
|
+
* {rx(({ read }) =>
|
|
275
|
+
* read(showDetails) ? read(details) : read(summary)
|
|
187
276
|
* )}
|
|
188
277
|
* </div>
|
|
189
278
|
* );
|
|
@@ -198,7 +287,7 @@ import { ContextSelectorFn } from '../core/select';
|
|
|
198
287
|
* return (
|
|
199
288
|
* <div>
|
|
200
289
|
* {rx(
|
|
201
|
-
* ({
|
|
290
|
+
* ({ read }) => read(user).name,
|
|
202
291
|
* (a, b) => a === b // Only re-render if name string changes
|
|
203
292
|
* )}
|
|
204
293
|
* </div>
|
|
@@ -216,7 +305,7 @@ import { ContextSelectorFn } from '../core/select';
|
|
|
216
305
|
* <Suspense fallback={<Loading />}>
|
|
217
306
|
* {rx(({ all }) => {
|
|
218
307
|
* // Use all() to wait for multiple atoms
|
|
219
|
-
* const [user, posts] = all([
|
|
308
|
+
* const [user, posts] = all([user$, posts$]);
|
|
220
309
|
* return <DashboardContent user={user} posts={posts} />;
|
|
221
310
|
* })}
|
|
222
311
|
* </Suspense>
|
|
@@ -246,5 +335,5 @@ import { ContextSelectorFn } from '../core/select';
|
|
|
246
335
|
* }
|
|
247
336
|
* ```
|
|
248
337
|
*/
|
|
249
|
-
export declare function rx<T
|
|
250
|
-
export declare function rx<T
|
|
338
|
+
export declare function rx<T extends ReactNode | PromiseLike<ReactNode>>(atom: Atom<T>, options?: Equality<T> | RxOptions<T>): ReactElement;
|
|
339
|
+
export declare function rx<T extends ReactNode | PromiseLike<ReactNode>>(selector: ReactiveSelector<T>, options?: Equality<T> | RxOptions<T>): ReactElement;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Pipeable } from '../core/types';
|
|
1
2
|
/**
|
|
2
3
|
* State for an action that hasn't been dispatched yet.
|
|
3
4
|
*/
|
|
@@ -59,7 +60,7 @@ export interface UseActionOptions {
|
|
|
59
60
|
/**
|
|
60
61
|
* Dependencies array. When lazy is false, re-executes when deps change.
|
|
61
62
|
* - Regular values: compared by reference (like useEffect deps)
|
|
62
|
-
* - Atoms: automatically tracked via
|
|
63
|
+
* - Atoms: automatically tracked via useSelector, re-executes when atom values change
|
|
63
64
|
* @default []
|
|
64
65
|
*/
|
|
65
66
|
deps?: unknown[];
|
|
@@ -67,7 +68,7 @@ export interface UseActionOptions {
|
|
|
67
68
|
/**
|
|
68
69
|
* Context passed to the action function.
|
|
69
70
|
*/
|
|
70
|
-
export interface ActionContext {
|
|
71
|
+
export interface ActionContext extends Pipeable {
|
|
71
72
|
/** AbortSignal for cancellation. New signal per dispatch. */
|
|
72
73
|
signal: AbortSignal;
|
|
73
74
|
}
|
|
@@ -229,11 +230,11 @@ export type Action<TResult, TLazy extends boolean = true> = ActionDispatch<TResu
|
|
|
229
230
|
*
|
|
230
231
|
* function UserProfile() {
|
|
231
232
|
* const fetchUser = useAction(
|
|
232
|
-
* async ({ signal }) => fetchUserApi(userIdAtom.
|
|
233
|
+
* async ({ signal }) => fetchUserApi(userIdAtom.get(), { signal }),
|
|
233
234
|
* { lazy: false, deps: [userIdAtom] }
|
|
234
235
|
* );
|
|
235
236
|
* // Automatically re-fetches when userIdAtom changes
|
|
236
|
-
* // Atoms in deps are tracked reactively via
|
|
237
|
+
* // Atoms in deps are tracked reactively via useSelector
|
|
237
238
|
* }
|
|
238
239
|
* ```
|
|
239
240
|
*
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ReactiveSelector } from '../core/select';
|
|
2
2
|
import { Atom, Equality } from '../core/types';
|
|
3
3
|
/**
|
|
4
4
|
* React hook that selects/derives a value from atom(s) with automatic subscriptions.
|
|
@@ -12,19 +12,52 @@ import { Atom, Equality } from '../core/types';
|
|
|
12
12
|
*
|
|
13
13
|
* ```tsx
|
|
14
14
|
* // ❌ WRONG - Don't use async function
|
|
15
|
-
*
|
|
15
|
+
* useSelector(async ({ read }) => {
|
|
16
16
|
* const data = await fetch('/api');
|
|
17
17
|
* return data;
|
|
18
18
|
* });
|
|
19
19
|
*
|
|
20
20
|
* // ❌ WRONG - Don't return a Promise
|
|
21
|
-
*
|
|
21
|
+
* useSelector(({ read }) => fetch('/api').then(r => r.json()));
|
|
22
22
|
*
|
|
23
|
-
* // ✅ CORRECT - Create async atom and read with
|
|
23
|
+
* // ✅ CORRECT - Create async atom and read with read()
|
|
24
24
|
* const data$ = atom(fetch('/api').then(r => r.json()));
|
|
25
|
-
*
|
|
25
|
+
* useSelector(({ read }) => read(data$)); // Suspends until resolved
|
|
26
26
|
* ```
|
|
27
27
|
*
|
|
28
|
+
* ## IMPORTANT: Do NOT Use try/catch - Use safe() Instead
|
|
29
|
+
*
|
|
30
|
+
* **Never wrap `read()` calls in try/catch blocks.** The `read()` function throws
|
|
31
|
+
* Promises when atoms are loading (Suspense pattern). A try/catch will catch
|
|
32
|
+
* these Promises and break the Suspense mechanism.
|
|
33
|
+
*
|
|
34
|
+
* ```tsx
|
|
35
|
+
* // ❌ WRONG - Catches Suspense Promise, breaks loading state
|
|
36
|
+
* const data = useSelector(({ read }) => {
|
|
37
|
+
* try {
|
|
38
|
+
* return read(asyncAtom$);
|
|
39
|
+
* } catch (e) {
|
|
40
|
+
* return null; // This catches BOTH errors AND loading promises!
|
|
41
|
+
* }
|
|
42
|
+
* });
|
|
43
|
+
*
|
|
44
|
+
* // ✅ CORRECT - Use safe() to catch errors but preserve Suspense
|
|
45
|
+
* const result = useSelector(({ read, safe }) => {
|
|
46
|
+
* const [err, data] = safe(() => {
|
|
47
|
+
* const raw = read(asyncAtom$); // Can throw Promise (Suspense)
|
|
48
|
+
* return JSON.parse(raw); // Can throw Error
|
|
49
|
+
* });
|
|
50
|
+
*
|
|
51
|
+
* if (err) return { error: err.message };
|
|
52
|
+
* return { data };
|
|
53
|
+
* });
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
* The `safe()` utility:
|
|
57
|
+
* - **Catches errors** and returns `[error, undefined]`
|
|
58
|
+
* - **Re-throws Promises** to preserve Suspense behavior
|
|
59
|
+
* - Returns `[undefined, result]` on success
|
|
60
|
+
*
|
|
28
61
|
* ## IMPORTANT: Suspense-Style API
|
|
29
62
|
*
|
|
30
63
|
* This hook uses a **Suspense-style API** for async atoms:
|
|
@@ -36,22 +69,19 @@ import { Atom, Equality } from '../core/types';
|
|
|
36
69
|
* - **You MUST wrap components with `<Suspense>`** to handle loading states
|
|
37
70
|
* - **You MUST wrap components with `<ErrorBoundary>`** to handle errors
|
|
38
71
|
*
|
|
39
|
-
* ## Alternative:
|
|
72
|
+
* ## Alternative: useAsyncState for Non-Suspense
|
|
40
73
|
*
|
|
41
|
-
* If you want to
|
|
74
|
+
* If you want to handle loading/error states imperatively without Suspense:
|
|
42
75
|
*
|
|
43
76
|
* ```tsx
|
|
77
|
+
* import { useAsyncState } from 'atomirx/react';
|
|
78
|
+
*
|
|
44
79
|
* function MyComponent() {
|
|
45
|
-
*
|
|
46
|
-
* const count = myDerivedAtom$.staleValue;
|
|
47
|
-
* const isLoading = isPending(myDerivedAtom$.value);
|
|
80
|
+
* const state = useAsyncState(myAtom$);
|
|
48
81
|
*
|
|
49
|
-
* return
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* Count: {count}
|
|
53
|
-
* </div>
|
|
54
|
-
* );
|
|
82
|
+
* if (state.status === "loading") return <Spinner />;
|
|
83
|
+
* if (state.status === "error") return <Error error={state.error} />;
|
|
84
|
+
* return <div>{state.value}</div>;
|
|
55
85
|
* }
|
|
56
86
|
* ```
|
|
57
87
|
*
|
|
@@ -59,14 +89,15 @@ import { Atom, Equality } from '../core/types';
|
|
|
59
89
|
* @param selectorOrAtom - Atom or context-based selector function (must return sync value)
|
|
60
90
|
* @param equals - Equality function or shorthand. Defaults to "shallow"
|
|
61
91
|
* @returns The selected value (Awaited<T>)
|
|
62
|
-
* @throws
|
|
92
|
+
* @throws Promise when loading (caught by Suspense)
|
|
93
|
+
* @throws Error when failed (caught by ErrorBoundary)
|
|
63
94
|
*
|
|
64
95
|
* @example Single atom (shorthand)
|
|
65
96
|
* ```tsx
|
|
66
97
|
* const count = atom(5);
|
|
67
98
|
*
|
|
68
99
|
* function Counter() {
|
|
69
|
-
* const value =
|
|
100
|
+
* const value = useSelector(count);
|
|
70
101
|
* return <div>{value}</div>;
|
|
71
102
|
* }
|
|
72
103
|
* ```
|
|
@@ -76,7 +107,7 @@ import { Atom, Equality } from '../core/types';
|
|
|
76
107
|
* const count = atom(5);
|
|
77
108
|
*
|
|
78
109
|
* function Counter() {
|
|
79
|
-
* const doubled =
|
|
110
|
+
* const doubled = useSelector(({ read }) => read(count) * 2);
|
|
80
111
|
* return <div>{doubled}</div>;
|
|
81
112
|
* }
|
|
82
113
|
* ```
|
|
@@ -87,8 +118,8 @@ import { Atom, Equality } from '../core/types';
|
|
|
87
118
|
* const lastName = atom("Doe");
|
|
88
119
|
*
|
|
89
120
|
* function FullName() {
|
|
90
|
-
* const fullName =
|
|
91
|
-
* `${
|
|
121
|
+
* const fullName = useSelector(({ read }) =>
|
|
122
|
+
* `${read(firstName)} ${read(lastName)}`
|
|
92
123
|
* );
|
|
93
124
|
* return <div>{fullName}</div>;
|
|
94
125
|
* }
|
|
@@ -99,7 +130,7 @@ import { Atom, Equality } from '../core/types';
|
|
|
99
130
|
* const userAtom = atom(fetchUser());
|
|
100
131
|
*
|
|
101
132
|
* function UserProfile() {
|
|
102
|
-
* const user =
|
|
133
|
+
* const user = useSelector(({ read }) => read(userAtom));
|
|
103
134
|
* return <div>{user.name}</div>;
|
|
104
135
|
* }
|
|
105
136
|
*
|
|
@@ -121,7 +152,7 @@ import { Atom, Equality } from '../core/types';
|
|
|
121
152
|
* const postsAtom = atom(fetchPosts());
|
|
122
153
|
*
|
|
123
154
|
* function Dashboard() {
|
|
124
|
-
* const data =
|
|
155
|
+
* const data = useSelector(({ all }) => {
|
|
125
156
|
* const [user, posts] = all(userAtom, postsAtom);
|
|
126
157
|
* return { user, posts };
|
|
127
158
|
* });
|
|
@@ -130,5 +161,5 @@ import { Atom, Equality } from '../core/types';
|
|
|
130
161
|
* }
|
|
131
162
|
* ```
|
|
132
163
|
*/
|
|
133
|
-
export declare function
|
|
134
|
-
export declare function
|
|
164
|
+
export declare function useSelector<T>(atom: Atom<T>, equals?: Equality<Awaited<T>>): Awaited<T>;
|
|
165
|
+
export declare function useSelector<T>(selector: ReactiveSelector<T>, equals?: Equality<T>): T;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "atomirx",
|
|
3
|
-
"
|
|
3
|
+
"description": "Opinionated, Batteries-Included Reactive State Management",
|
|
4
|
+
"keywords": [
|
|
5
|
+
"reactive",
|
|
6
|
+
"state",
|
|
7
|
+
"management",
|
|
8
|
+
"atom",
|
|
9
|
+
"derived",
|
|
10
|
+
"effect"
|
|
11
|
+
],
|
|
12
|
+
"author": "Gignuyen",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/linq2js/atomirx"
|
|
17
|
+
},
|
|
18
|
+
"homepage": "https://github.com/linq2js/atomirx",
|
|
19
|
+
"version": "0.0.4",
|
|
4
20
|
"type": "module",
|
|
5
21
|
"main": "./dist/atomirx.umd.cjs",
|
|
6
22
|
"module": "./dist/atomirx.js",
|