atomirx 0.0.2 → 0.0.5
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 +868 -161
- package/coverage/src/core/onCreateHook.ts.html +72 -70
- package/dist/core/atom.d.ts +83 -6
- package/dist/core/batch.d.ts +3 -3
- package/dist/core/derived.d.ts +69 -22
- package/dist/core/effect.d.ts +52 -52
- package/dist/core/getAtomState.d.ts +29 -0
- package/dist/core/hook.d.ts +1 -1
- package/dist/core/onCreateHook.d.ts +37 -23
- package/dist/core/onErrorHook.d.ts +49 -0
- package/dist/core/promiseCache.d.ts +23 -32
- package/dist/core/select.d.ts +208 -29
- package/dist/core/types.d.ts +107 -22
- package/dist/core/withReady.d.ts +115 -0
- package/dist/core/withReady.test.d.ts +1 -0
- package/dist/index-CBVj1kSj.js +1350 -0
- package/dist/index-Cxk9v0um.cjs +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +12 -8
- package/dist/index.js +18 -15
- package/dist/react/index.cjs +10 -10
- package/dist/react/index.d.ts +2 -1
- package/dist/react/index.js +422 -377
- 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 +1 -1
- package/src/core/atom.test.ts +307 -43
- package/src/core/atom.ts +144 -22
- package/src/core/batch.test.ts +10 -10
- package/src/core/batch.ts +3 -3
- package/src/core/define.test.ts +12 -11
- package/src/core/define.ts +1 -1
- package/src/core/derived.test.ts +906 -72
- package/src/core/derived.ts +192 -81
- package/src/core/effect.test.ts +651 -45
- package/src/core/effect.ts +102 -98
- package/src/core/getAtomState.ts +69 -0
- package/src/core/hook.test.ts +5 -5
- package/src/core/hook.ts +1 -1
- package/src/core/onCreateHook.ts +38 -23
- package/src/core/onErrorHook.test.ts +350 -0
- package/src/core/onErrorHook.ts +52 -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 +107 -29
- package/src/core/withReady.test.ts +534 -0
- package/src/core/withReady.ts +191 -0
- package/src/core/withUse.ts +1 -1
- package/src/index.test.ts +4 -4
- package/src/index.ts +21 -7
- 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/onErrorHook.test.d.ts} +0 -0
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { describe, it, expect, vi } from "vitest";
|
|
2
2
|
import { renderHook, act, waitFor } from "@testing-library/react";
|
|
3
3
|
import { atom } from "../core/atom";
|
|
4
|
-
import {
|
|
4
|
+
import { useSelector } from "./useSelector";
|
|
5
5
|
|
|
6
|
-
describe("
|
|
6
|
+
describe("useSelector", () => {
|
|
7
7
|
describe("basic functionality", () => {
|
|
8
8
|
it("should read value from sync atom", () => {
|
|
9
9
|
const count$ = atom(5);
|
|
10
|
-
const { result } = renderHook(() =>
|
|
10
|
+
const { result } = renderHook(() => useSelector(count$));
|
|
11
11
|
expect(result.current).toBe(5);
|
|
12
12
|
});
|
|
13
13
|
|
|
14
14
|
it("should update when atom value changes", async () => {
|
|
15
15
|
const count$ = atom(0);
|
|
16
|
-
const { result } = renderHook(() =>
|
|
16
|
+
const { result } = renderHook(() => useSelector(count$));
|
|
17
17
|
|
|
18
18
|
expect(result.current).toBe(0);
|
|
19
19
|
|
|
@@ -28,7 +28,7 @@ describe("useValue", () => {
|
|
|
28
28
|
|
|
29
29
|
it("should work with object values", () => {
|
|
30
30
|
const user$ = atom({ name: "John", age: 30 });
|
|
31
|
-
const { result } = renderHook(() =>
|
|
31
|
+
const { result } = renderHook(() => useSelector(user$));
|
|
32
32
|
|
|
33
33
|
expect(result.current).toEqual({ name: "John", age: 30 });
|
|
34
34
|
});
|
|
@@ -38,7 +38,7 @@ describe("useValue", () => {
|
|
|
38
38
|
it("should support selector function", () => {
|
|
39
39
|
const count$ = atom(5);
|
|
40
40
|
const { result } = renderHook(() =>
|
|
41
|
-
|
|
41
|
+
useSelector(({ read }) => read(count$) * 2)
|
|
42
42
|
);
|
|
43
43
|
|
|
44
44
|
expect(result.current).toBe(10);
|
|
@@ -48,7 +48,7 @@ describe("useValue", () => {
|
|
|
48
48
|
const a$ = atom(2);
|
|
49
49
|
const b$ = atom(3);
|
|
50
50
|
const { result } = renderHook(() =>
|
|
51
|
-
|
|
51
|
+
useSelector(({ read }) => read(a$) + read(b$))
|
|
52
52
|
);
|
|
53
53
|
|
|
54
54
|
expect(result.current).toBe(5);
|
|
@@ -58,7 +58,7 @@ describe("useValue", () => {
|
|
|
58
58
|
const a$ = atom(2);
|
|
59
59
|
const b$ = atom(3);
|
|
60
60
|
const { result } = renderHook(() =>
|
|
61
|
-
|
|
61
|
+
useSelector(({ read }) => read(a$) * read(b$))
|
|
62
62
|
);
|
|
63
63
|
|
|
64
64
|
expect(result.current).toBe(6);
|
|
@@ -80,8 +80,8 @@ describe("useValue", () => {
|
|
|
80
80
|
const details$ = atom("Detailed");
|
|
81
81
|
|
|
82
82
|
const { result } = renderHook(() =>
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
useSelector(({ read }) =>
|
|
84
|
+
read(showDetails$) ? read(details$) : read(summary$)
|
|
85
85
|
)
|
|
86
86
|
);
|
|
87
87
|
|
|
@@ -104,7 +104,7 @@ describe("useValue", () => {
|
|
|
104
104
|
|
|
105
105
|
const { result } = renderHook(() => {
|
|
106
106
|
renderCount();
|
|
107
|
-
return
|
|
107
|
+
return useSelector(source$);
|
|
108
108
|
});
|
|
109
109
|
|
|
110
110
|
expect(result.current).toEqual({ a: 1 });
|
|
@@ -120,7 +120,7 @@ describe("useValue", () => {
|
|
|
120
120
|
it("should support custom equality", async () => {
|
|
121
121
|
const source$ = atom({ id: 1, name: "John" });
|
|
122
122
|
const { result } = renderHook(() =>
|
|
123
|
-
|
|
123
|
+
useSelector(source$, (a, b) => a.id === b.id)
|
|
124
124
|
);
|
|
125
125
|
|
|
126
126
|
expect(result.current.name).toBe("John");
|
|
@@ -140,8 +140,8 @@ describe("useValue", () => {
|
|
|
140
140
|
const c$ = atom(3);
|
|
141
141
|
|
|
142
142
|
const { result } = renderHook(() =>
|
|
143
|
-
|
|
144
|
-
const [a, b, c] = all(a$, b$, c$);
|
|
143
|
+
useSelector(({ all }) => {
|
|
144
|
+
const [a, b, c] = all([a$, b$, c$]);
|
|
145
145
|
return a + b + c;
|
|
146
146
|
})
|
|
147
147
|
);
|
|
@@ -153,7 +153,7 @@ describe("useValue", () => {
|
|
|
153
153
|
describe("cleanup", () => {
|
|
154
154
|
it("should unsubscribe on unmount", async () => {
|
|
155
155
|
const count$ = atom(0);
|
|
156
|
-
const { unmount } = renderHook(() =>
|
|
156
|
+
const { unmount } = renderHook(() => useSelector(count$));
|
|
157
157
|
|
|
158
158
|
unmount();
|
|
159
159
|
|
|
@@ -171,7 +171,7 @@ describe("useValue", () => {
|
|
|
171
171
|
|
|
172
172
|
describe("async atoms", () => {
|
|
173
173
|
it("should throw promise for pending atom (Suspense)", () => {
|
|
174
|
-
// When an atom's value is a pending Promise,
|
|
174
|
+
// When an atom's value is a pending Promise, useSelector should throw
|
|
175
175
|
// the Promise to trigger Suspense. This is hard to test without
|
|
176
176
|
// proper Suspense boundary setup.
|
|
177
177
|
// The hook will throw the Promise which is caught by Suspense
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { resolveEquality } from "../core/equality";
|
|
1
|
+
import { useCallback, useRef, useSyncExternalStore } from "react";
|
|
2
|
+
import { ReactiveSelector, select } from "../core/select";
|
|
4
3
|
import { Atom, Equality } from "../core/types";
|
|
4
|
+
import { resolveEquality } from "../core/equality";
|
|
5
5
|
import { isAtom } from "../core/isAtom";
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -16,19 +16,52 @@ import { isAtom } from "../core/isAtom";
|
|
|
16
16
|
*
|
|
17
17
|
* ```tsx
|
|
18
18
|
* // ❌ WRONG - Don't use async function
|
|
19
|
-
*
|
|
19
|
+
* useSelector(async ({ read }) => {
|
|
20
20
|
* const data = await fetch('/api');
|
|
21
21
|
* return data;
|
|
22
22
|
* });
|
|
23
23
|
*
|
|
24
24
|
* // ❌ WRONG - Don't return a Promise
|
|
25
|
-
*
|
|
25
|
+
* useSelector(({ read }) => fetch('/api').then(r => r.json()));
|
|
26
26
|
*
|
|
27
|
-
* // ✅ CORRECT - Create async atom and read with
|
|
27
|
+
* // ✅ CORRECT - Create async atom and read with read()
|
|
28
28
|
* const data$ = atom(fetch('/api').then(r => r.json()));
|
|
29
|
-
*
|
|
29
|
+
* useSelector(({ read }) => read(data$)); // Suspends until resolved
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* ## IMPORTANT: Do NOT Use try/catch - Use safe() Instead
|
|
33
|
+
*
|
|
34
|
+
* **Never wrap `read()` calls in try/catch blocks.** The `read()` function throws
|
|
35
|
+
* Promises when atoms are loading (Suspense pattern). A try/catch will catch
|
|
36
|
+
* these Promises and break the Suspense mechanism.
|
|
37
|
+
*
|
|
38
|
+
* ```tsx
|
|
39
|
+
* // ❌ WRONG - Catches Suspense Promise, breaks loading state
|
|
40
|
+
* const data = useSelector(({ read }) => {
|
|
41
|
+
* try {
|
|
42
|
+
* return read(asyncAtom$);
|
|
43
|
+
* } catch (e) {
|
|
44
|
+
* return null; // This catches BOTH errors AND loading promises!
|
|
45
|
+
* }
|
|
46
|
+
* });
|
|
47
|
+
*
|
|
48
|
+
* // ✅ CORRECT - Use safe() to catch errors but preserve Suspense
|
|
49
|
+
* const result = useSelector(({ read, safe }) => {
|
|
50
|
+
* const [err, data] = safe(() => {
|
|
51
|
+
* const raw = read(asyncAtom$); // Can throw Promise (Suspense)
|
|
52
|
+
* return JSON.parse(raw); // Can throw Error
|
|
53
|
+
* });
|
|
54
|
+
*
|
|
55
|
+
* if (err) return { error: err.message };
|
|
56
|
+
* return { data };
|
|
57
|
+
* });
|
|
30
58
|
* ```
|
|
31
59
|
*
|
|
60
|
+
* The `safe()` utility:
|
|
61
|
+
* - **Catches errors** and returns `[error, undefined]`
|
|
62
|
+
* - **Re-throws Promises** to preserve Suspense behavior
|
|
63
|
+
* - Returns `[undefined, result]` on success
|
|
64
|
+
*
|
|
32
65
|
* ## IMPORTANT: Suspense-Style API
|
|
33
66
|
*
|
|
34
67
|
* This hook uses a **Suspense-style API** for async atoms:
|
|
@@ -40,22 +73,19 @@ import { isAtom } from "../core/isAtom";
|
|
|
40
73
|
* - **You MUST wrap components with `<Suspense>`** to handle loading states
|
|
41
74
|
* - **You MUST wrap components with `<ErrorBoundary>`** to handle errors
|
|
42
75
|
*
|
|
43
|
-
* ## Alternative:
|
|
76
|
+
* ## Alternative: useAsyncState for Non-Suspense
|
|
44
77
|
*
|
|
45
|
-
* If you want to
|
|
78
|
+
* If you want to handle loading/error states imperatively without Suspense:
|
|
46
79
|
*
|
|
47
80
|
* ```tsx
|
|
81
|
+
* import { useAsyncState } from 'atomirx/react';
|
|
82
|
+
*
|
|
48
83
|
* function MyComponent() {
|
|
49
|
-
*
|
|
50
|
-
* const count = myDerivedAtom$.staleValue;
|
|
51
|
-
* const isLoading = isPending(myDerivedAtom$.value);
|
|
84
|
+
* const state = useAsyncState(myAtom$);
|
|
52
85
|
*
|
|
53
|
-
* return
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* Count: {count}
|
|
57
|
-
* </div>
|
|
58
|
-
* );
|
|
86
|
+
* if (state.status === "loading") return <Spinner />;
|
|
87
|
+
* if (state.status === "error") return <Error error={state.error} />;
|
|
88
|
+
* return <div>{state.value}</div>;
|
|
59
89
|
* }
|
|
60
90
|
* ```
|
|
61
91
|
*
|
|
@@ -63,14 +93,15 @@ import { isAtom } from "../core/isAtom";
|
|
|
63
93
|
* @param selectorOrAtom - Atom or context-based selector function (must return sync value)
|
|
64
94
|
* @param equals - Equality function or shorthand. Defaults to "shallow"
|
|
65
95
|
* @returns The selected value (Awaited<T>)
|
|
66
|
-
* @throws
|
|
96
|
+
* @throws Promise when loading (caught by Suspense)
|
|
97
|
+
* @throws Error when failed (caught by ErrorBoundary)
|
|
67
98
|
*
|
|
68
99
|
* @example Single atom (shorthand)
|
|
69
100
|
* ```tsx
|
|
70
101
|
* const count = atom(5);
|
|
71
102
|
*
|
|
72
103
|
* function Counter() {
|
|
73
|
-
* const value =
|
|
104
|
+
* const value = useSelector(count);
|
|
74
105
|
* return <div>{value}</div>;
|
|
75
106
|
* }
|
|
76
107
|
* ```
|
|
@@ -80,7 +111,7 @@ import { isAtom } from "../core/isAtom";
|
|
|
80
111
|
* const count = atom(5);
|
|
81
112
|
*
|
|
82
113
|
* function Counter() {
|
|
83
|
-
* const doubled =
|
|
114
|
+
* const doubled = useSelector(({ read }) => read(count) * 2);
|
|
84
115
|
* return <div>{doubled}</div>;
|
|
85
116
|
* }
|
|
86
117
|
* ```
|
|
@@ -91,8 +122,8 @@ import { isAtom } from "../core/isAtom";
|
|
|
91
122
|
* const lastName = atom("Doe");
|
|
92
123
|
*
|
|
93
124
|
* function FullName() {
|
|
94
|
-
* const fullName =
|
|
95
|
-
* `${
|
|
125
|
+
* const fullName = useSelector(({ read }) =>
|
|
126
|
+
* `${read(firstName)} ${read(lastName)}`
|
|
96
127
|
* );
|
|
97
128
|
* return <div>{fullName}</div>;
|
|
98
129
|
* }
|
|
@@ -103,7 +134,7 @@ import { isAtom } from "../core/isAtom";
|
|
|
103
134
|
* const userAtom = atom(fetchUser());
|
|
104
135
|
*
|
|
105
136
|
* function UserProfile() {
|
|
106
|
-
* const user =
|
|
137
|
+
* const user = useSelector(({ read }) => read(userAtom));
|
|
107
138
|
* return <div>{user.name}</div>;
|
|
108
139
|
* }
|
|
109
140
|
*
|
|
@@ -125,7 +156,7 @@ import { isAtom } from "../core/isAtom";
|
|
|
125
156
|
* const postsAtom = atom(fetchPosts());
|
|
126
157
|
*
|
|
127
158
|
* function Dashboard() {
|
|
128
|
-
* const data =
|
|
159
|
+
* const data = useSelector(({ all }) => {
|
|
129
160
|
* const [user, posts] = all(userAtom, postsAtom);
|
|
130
161
|
* return { user, posts };
|
|
131
162
|
* });
|
|
@@ -135,25 +166,25 @@ import { isAtom } from "../core/isAtom";
|
|
|
135
166
|
* ```
|
|
136
167
|
*/
|
|
137
168
|
// Overload: Pass atom directly
|
|
138
|
-
export function
|
|
169
|
+
export function useSelector<T>(
|
|
139
170
|
atom: Atom<T>,
|
|
140
171
|
equals?: Equality<Awaited<T>>
|
|
141
172
|
): Awaited<T>;
|
|
142
173
|
|
|
143
174
|
// Overload: Context-based selector function
|
|
144
|
-
export function
|
|
145
|
-
selector:
|
|
175
|
+
export function useSelector<T>(
|
|
176
|
+
selector: ReactiveSelector<T>,
|
|
146
177
|
equals?: Equality<T>
|
|
147
178
|
): T;
|
|
148
179
|
|
|
149
|
-
export function
|
|
150
|
-
selectorOrAtom:
|
|
180
|
+
export function useSelector<T>(
|
|
181
|
+
selectorOrAtom: ReactiveSelector<T> | Atom<T>,
|
|
151
182
|
equals?: Equality<T>
|
|
152
183
|
): T {
|
|
153
184
|
// Convert atom shorthand to context selector
|
|
154
|
-
const selector:
|
|
155
|
-
? ({
|
|
156
|
-
: (selectorOrAtom as
|
|
185
|
+
const selector: ReactiveSelector<T> = isAtom(selectorOrAtom)
|
|
186
|
+
? ({ read }) => read(selectorOrAtom as Atom<T>) as T
|
|
187
|
+
: (selectorOrAtom as ReactiveSelector<T>);
|
|
157
188
|
|
|
158
189
|
// Default to shallow equality
|
|
159
190
|
const eq = resolveEquality((equals as Equality<unknown>) ?? "shallow");
|
package/v2.md
CHANGED
|
@@ -108,13 +108,13 @@ Computed value. Always returns `Promise<T>` for `.value`.
|
|
|
108
108
|
// Without fallback - staleValue is T | undefined
|
|
109
109
|
function derived<T>(
|
|
110
110
|
compute: (ctx: SelectContext) => T,
|
|
111
|
-
options?: DerivedOptions
|
|
111
|
+
options?: DerivedOptions
|
|
112
112
|
): DerivedAtom<T, false>;
|
|
113
113
|
|
|
114
114
|
// With fallback - staleValue is guaranteed T
|
|
115
115
|
function derived<T>(
|
|
116
116
|
compute: (ctx: SelectContext) => T,
|
|
117
|
-
options: DerivedOptions & { fallback: T }
|
|
117
|
+
options: DerivedOptions & { fallback: T }
|
|
118
118
|
): DerivedAtom<T, true>;
|
|
119
119
|
|
|
120
120
|
interface DerivedOptions<T> {
|
|
@@ -387,12 +387,12 @@ function any<A extends AnyAtom<unknown>[]>(...atoms: A): AtomValue<A[number]> {
|
|
|
387
387
|
}
|
|
388
388
|
```
|
|
389
389
|
|
|
390
|
-
| Scenario | Result
|
|
391
|
-
| -------------- |
|
|
392
|
-
| Any sync value | return first sync value
|
|
393
|
-
| Any ready | return first value
|
|
394
|
-
| Any loading | throw Promise (wait for potential)
|
|
395
|
-
| All errored | throw `AggregateError`
|
|
390
|
+
| Scenario | Result |
|
|
391
|
+
| -------------- | ---------------------------------- |
|
|
392
|
+
| Any sync value | return first sync value |
|
|
393
|
+
| Any ready | return first value |
|
|
394
|
+
| Any loading | throw Promise (wait for potential) |
|
|
395
|
+
| All errored | throw `AggregateError` |
|
|
396
396
|
|
|
397
397
|
> **Note:** `any()` does NOT use fallback - it waits for a real ready value.
|
|
398
398
|
|
|
@@ -437,22 +437,22 @@ function settled<A extends AnyAtom<unknown>[]>(
|
|
|
437
437
|
}
|
|
438
438
|
```
|
|
439
439
|
|
|
440
|
-
| Scenario | Result
|
|
441
|
-
| ----------- |
|
|
442
|
-
| All settled | return `[{ status, value/error }, ...]`
|
|
443
|
-
| Any loading | throw Promise
|
|
440
|
+
| Scenario | Result |
|
|
441
|
+
| ----------- | --------------------------------------- |
|
|
442
|
+
| All settled | return `[{ status, value/error }, ...]` |
|
|
443
|
+
| Any loading | throw Promise |
|
|
444
444
|
|
|
445
445
|
### SelectContext Summary
|
|
446
446
|
|
|
447
447
|
All methods use `getAtomState()` internally.
|
|
448
448
|
|
|
449
|
-
| Method | Returns | Throws on Error?
|
|
450
|
-
| ------------------- | --------------- |
|
|
451
|
-
| `get(atom)` | Single value | Yes
|
|
452
|
-
| `all(...atoms)` | Array of values | Yes (first error)
|
|
453
|
-
| `race(...atoms)` | First settled | Yes (if first)
|
|
454
|
-
| `any(...atoms)` | First ready | Only if all error
|
|
455
|
-
| `settled(...atoms)` | Array of status | No
|
|
449
|
+
| Method | Returns | Throws on Error? |
|
|
450
|
+
| ------------------- | --------------- | ----------------- |
|
|
451
|
+
| `get(atom)` | Single value | Yes |
|
|
452
|
+
| `all(...atoms)` | Array of values | Yes (first error) |
|
|
453
|
+
| `race(...atoms)` | First settled | Yes (if first) |
|
|
454
|
+
| `any(...atoms)` | First ready | Only if all error |
|
|
455
|
+
| `settled(...atoms)` | Array of status | No |
|
|
456
456
|
|
|
457
457
|
> **Note:** `race()` and `any()` are typically used with `MutableAtom<Promise<T>>` for racing data sources.
|
|
458
458
|
|
|
@@ -543,7 +543,7 @@ interface EffectContext extends SelectContext {
|
|
|
543
543
|
|
|
544
544
|
function effect(
|
|
545
545
|
fn: (ctx: EffectContext) => void,
|
|
546
|
-
options?: EffectOptions
|
|
546
|
+
options?: EffectOptions
|
|
547
547
|
): () => void;
|
|
548
548
|
|
|
549
549
|
interface EffectOptions {
|
|
@@ -585,7 +585,7 @@ const dispose = effect(
|
|
|
585
585
|
},
|
|
586
586
|
{
|
|
587
587
|
onError: (e) => console.error("Unhandled error:", e),
|
|
588
|
-
}
|
|
588
|
+
}
|
|
589
589
|
);
|
|
590
590
|
```
|
|
591
591
|
|
|
@@ -593,13 +593,13 @@ const dispose = effect(
|
|
|
593
593
|
|
|
594
594
|
## React Hooks
|
|
595
595
|
|
|
596
|
-
###
|
|
596
|
+
### useSelector
|
|
597
597
|
|
|
598
598
|
```typescript
|
|
599
|
-
function
|
|
600
|
-
function
|
|
599
|
+
function useSelector<T>(atom: Atom<T>): Awaited<T>;
|
|
600
|
+
function useSelector<T>(
|
|
601
601
|
selector: (ctx: SelectContext) => T,
|
|
602
|
-
equals?: Equality<Awaited<T
|
|
602
|
+
equals?: Equality<Awaited<T>>
|
|
603
603
|
): Awaited<T>;
|
|
604
604
|
```
|
|
605
605
|
|
|
@@ -608,7 +608,7 @@ function useValue<T>(
|
|
|
608
608
|
```typescript
|
|
609
609
|
// With DerivedAtom (Suspense handles loading)
|
|
610
610
|
function PostCount() {
|
|
611
|
-
const count =
|
|
611
|
+
const count = useSelector(postCount$); // number (awaited)
|
|
612
612
|
return <div>Count: {count}</div>;
|
|
613
613
|
}
|
|
614
614
|
|
|
@@ -688,7 +688,7 @@ derived<T>(compute, { fallback }): DerivedAtom<T, true>
|
|
|
688
688
|
effect(fn, options?): () => void
|
|
689
689
|
|
|
690
690
|
// React
|
|
691
|
-
|
|
691
|
+
useSelector(source): T | Awaited<T>
|
|
692
692
|
|
|
693
693
|
// Types
|
|
694
694
|
Atom<T>
|
|
@@ -701,25 +701,25 @@ SelectContext
|
|
|
701
701
|
|
|
702
702
|
## Summary Table
|
|
703
703
|
|
|
704
|
-
|
|
|
705
|
-
|
|
|
706
|
-
| Purpose
|
|
707
|
-
| `.value`
|
|
708
|
-
| `.staleValue`
|
|
709
|
-
| `.state()`
|
|
710
|
-
| `.set()`
|
|
711
|
-
| `.reset()`
|
|
712
|
-
| `.refresh()`
|
|
713
|
-
| `.on()`
|
|
704
|
+
| | MutableAtom | DerivedAtom | DerivedAtom with fallback |
|
|
705
|
+
| ------------- | ----------- | ------------------- | ------------------------- |
|
|
706
|
+
| Purpose | Raw storage | Computed (async) | Computed + sync access |
|
|
707
|
+
| `.value` | `T` (raw) | `Promise<T>` | `Promise<T>` |
|
|
708
|
+
| `.staleValue` | ❌ | ✅ `T \| undefined` | ✅ `T` |
|
|
709
|
+
| `.state()` | ❌ | ✅ `AtomState<T>` | ✅ `AtomState<T>` |
|
|
710
|
+
| `.set()` | ✅ | ❌ | ❌ |
|
|
711
|
+
| `.reset()` | ✅ | ❌ | ❌ |
|
|
712
|
+
| `.refresh()` | ❌ | ✅ | ✅ |
|
|
713
|
+
| `.on()` | ✅ | ✅ | ✅ |
|
|
714
714
|
|
|
715
715
|
---
|
|
716
716
|
|
|
717
717
|
## Migration from v1
|
|
718
718
|
|
|
719
|
-
| v1
|
|
720
|
-
|
|
|
721
|
-
| `atom(promise, { fallback })`
|
|
722
|
-
| `atom.loading`
|
|
723
|
-
| `atom.error`
|
|
724
|
-
| `atom.stale()`
|
|
725
|
-
| `
|
|
719
|
+
| v1 | v2 |
|
|
720
|
+
| --------------------------------- | --------------------------------------------- |
|
|
721
|
+
| `atom(promise, { fallback })` | `atom(promise)` (no fallback on atom) |
|
|
722
|
+
| `atom.loading` | `isPending(derived$.value)` from promiseCache |
|
|
723
|
+
| `atom.error` | `await derived.value` throws |
|
|
724
|
+
| `atom.stale()` | `isPending(derived$.value)` from promiseCache |
|
|
725
|
+
| `useSelector(atom)` with Suspense | `useSelector(derived)` with Suspense |
|