atomirx 0.0.7 → 0.1.0
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 +198 -2234
- package/bin/cli.js +90 -0
- package/dist/core/derived.d.ts +2 -2
- package/dist/core/effect.d.ts +3 -2
- package/dist/core/onCreateHook.d.ts +15 -2
- package/dist/core/onErrorHook.d.ts +4 -1
- package/dist/core/pool.d.ts +78 -0
- package/dist/core/pool.test.d.ts +1 -0
- package/dist/core/select-boolean.test.d.ts +1 -0
- package/dist/core/select-pool.test.d.ts +1 -0
- package/dist/core/select.d.ts +278 -86
- package/dist/core/types.d.ts +233 -1
- package/dist/core/withAbort.d.ts +95 -0
- package/dist/core/withReady.d.ts +3 -3
- package/dist/devtools/constants.d.ts +41 -0
- package/dist/devtools/index.cjs +1 -0
- package/dist/devtools/index.d.ts +29 -0
- package/dist/devtools/index.js +429 -0
- package/dist/devtools/registry.d.ts +98 -0
- package/dist/devtools/registry.test.d.ts +1 -0
- package/dist/devtools/setup.d.ts +61 -0
- package/dist/devtools/types.d.ts +311 -0
- package/dist/index-BZEnfIcB.cjs +1 -0
- package/dist/index-BbPZhsDl.js +1653 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +18 -14
- package/dist/onDispatchHook-C8yLzr-o.cjs +1 -0
- package/dist/onDispatchHook-SKbiIUaJ.js +5 -0
- package/dist/onErrorHook-BGGy3tqK.js +38 -0
- package/dist/onErrorHook-DHBASmYw.cjs +1 -0
- package/dist/react/index.cjs +1 -30
- package/dist/react/index.js +206 -791
- package/dist/react/onDispatchHook.d.ts +106 -0
- package/dist/react/useAction.d.ts +4 -1
- package/dist/react-devtools/DevToolsPanel.d.ts +93 -0
- package/dist/react-devtools/EntityDetails.d.ts +10 -0
- package/dist/react-devtools/EntityList.d.ts +15 -0
- package/dist/react-devtools/LogList.d.ts +12 -0
- package/dist/react-devtools/hooks.d.ts +50 -0
- package/dist/react-devtools/index.cjs +1 -0
- package/dist/react-devtools/index.d.ts +31 -0
- package/dist/react-devtools/index.js +1589 -0
- package/dist/react-devtools/styles.d.ts +148 -0
- package/package.json +26 -2
- package/skills/atomirx/SKILL.md +456 -0
- package/skills/atomirx/references/async-patterns.md +188 -0
- package/skills/atomirx/references/atom-patterns.md +238 -0
- package/skills/atomirx/references/deferred-loading.md +191 -0
- package/skills/atomirx/references/derived-patterns.md +428 -0
- package/skills/atomirx/references/effect-patterns.md +426 -0
- package/skills/atomirx/references/error-handling.md +140 -0
- package/skills/atomirx/references/hooks.md +322 -0
- package/skills/atomirx/references/pool-patterns.md +229 -0
- package/skills/atomirx/references/react-integration.md +411 -0
- package/skills/atomirx/references/rules.md +407 -0
- package/skills/atomirx/references/select-context.md +309 -0
- package/skills/atomirx/references/service-template.md +172 -0
- package/skills/atomirx/references/store-template.md +205 -0
- package/skills/atomirx/references/testing-patterns.md +431 -0
- package/coverage/base.css +0 -224
- package/coverage/block-navigation.js +0 -87
- package/coverage/clover.xml +0 -1440
- package/coverage/coverage-final.json +0 -14
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +0 -131
- package/coverage/prettify.css +0 -1
- package/coverage/prettify.js +0 -2
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +0 -210
- package/coverage/src/core/atom.ts.html +0 -889
- package/coverage/src/core/batch.ts.html +0 -223
- package/coverage/src/core/define.ts.html +0 -805
- package/coverage/src/core/emitter.ts.html +0 -919
- package/coverage/src/core/equality.ts.html +0 -631
- package/coverage/src/core/hook.ts.html +0 -460
- package/coverage/src/core/index.html +0 -281
- package/coverage/src/core/isAtom.ts.html +0 -100
- package/coverage/src/core/isPromiseLike.ts.html +0 -133
- package/coverage/src/core/onCreateHook.ts.html +0 -138
- package/coverage/src/core/scheduleNotifyHook.ts.html +0 -94
- package/coverage/src/core/types.ts.html +0 -523
- package/coverage/src/core/withUse.ts.html +0 -253
- package/coverage/src/index.html +0 -116
- package/coverage/src/index.ts.html +0 -106
- package/dist/index-CBVj1kSj.js +0 -1350
- package/dist/index-Cxk9v0um.cjs +0 -1
- package/scripts/publish.js +0 -198
- package/src/core/atom.test.ts +0 -633
- package/src/core/atom.ts +0 -311
- package/src/core/atomState.test.ts +0 -342
- package/src/core/atomState.ts +0 -256
- package/src/core/batch.test.ts +0 -257
- package/src/core/batch.ts +0 -172
- package/src/core/define.test.ts +0 -343
- package/src/core/define.ts +0 -243
- package/src/core/derived.test.ts +0 -1215
- package/src/core/derived.ts +0 -450
- package/src/core/effect.test.ts +0 -802
- package/src/core/effect.ts +0 -188
- package/src/core/emitter.test.ts +0 -364
- package/src/core/emitter.ts +0 -392
- package/src/core/equality.test.ts +0 -392
- package/src/core/equality.ts +0 -182
- package/src/core/getAtomState.ts +0 -69
- package/src/core/hook.test.ts +0 -227
- package/src/core/hook.ts +0 -177
- package/src/core/isAtom.ts +0 -27
- package/src/core/isPromiseLike.test.ts +0 -72
- package/src/core/isPromiseLike.ts +0 -16
- package/src/core/onCreateHook.ts +0 -107
- package/src/core/onErrorHook.test.ts +0 -350
- package/src/core/onErrorHook.ts +0 -52
- package/src/core/promiseCache.test.ts +0 -241
- package/src/core/promiseCache.ts +0 -284
- package/src/core/scheduleNotifyHook.ts +0 -53
- package/src/core/select.ts +0 -729
- package/src/core/selector.test.ts +0 -799
- package/src/core/types.ts +0 -389
- package/src/core/withReady.test.ts +0 -534
- package/src/core/withReady.ts +0 -191
- package/src/core/withUse.test.ts +0 -249
- package/src/core/withUse.ts +0 -56
- package/src/index.test.ts +0 -80
- package/src/index.ts +0 -65
- package/src/react/index.ts +0 -21
- package/src/react/rx.test.tsx +0 -571
- package/src/react/rx.tsx +0 -531
- package/src/react/strictModeTest.tsx +0 -71
- package/src/react/useAction.test.ts +0 -987
- package/src/react/useAction.ts +0 -607
- package/src/react/useSelector.test.ts +0 -182
- package/src/react/useSelector.ts +0 -292
- package/src/react/useStable.test.ts +0 -553
- package/src/react/useStable.ts +0 -288
- package/tsconfig.json +0 -9
- package/v2.md +0 -725
- package/vite.config.ts +0 -39
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# Service Template
|
|
2
|
+
|
|
3
|
+
Services = stateless modules. NO atoms. Pure functions only.
|
|
4
|
+
|
|
5
|
+
## Template
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
// services/[name]/[name].service.ts
|
|
9
|
+
import { define } from "atomirx";
|
|
10
|
+
|
|
11
|
+
// ==================== Types ====================
|
|
12
|
+
|
|
13
|
+
export interface EntityDTO {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
createdAt: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface CreateEntityInput {
|
|
20
|
+
name: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface UpdateEntityInput {
|
|
24
|
+
name?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface EntityService {
|
|
28
|
+
fetch: (id: string) => Promise<EntityDTO>;
|
|
29
|
+
list: (params?: { limit?: number; offset?: number }) => Promise<EntityDTO[]>;
|
|
30
|
+
create: (input: CreateEntityInput) => Promise<EntityDTO>;
|
|
31
|
+
update: (id: string, input: UpdateEntityInput) => Promise<EntityDTO>;
|
|
32
|
+
delete: (id: string) => Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ==================== Service ====================
|
|
36
|
+
|
|
37
|
+
export const entityService = define((): EntityService => {
|
|
38
|
+
const BASE_URL = "/api/entities";
|
|
39
|
+
|
|
40
|
+
const handleResponse = async <T>(response: Response): Promise<T> => {
|
|
41
|
+
if (!response.ok) {
|
|
42
|
+
const error = await response.json().catch(() => ({ message: "Request failed" }));
|
|
43
|
+
throw new Error(error.message || `HTTP ${response.status}`);
|
|
44
|
+
}
|
|
45
|
+
return response.json();
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
fetch: async (id) => {
|
|
50
|
+
const res = await fetch(`${BASE_URL}/${id}`);
|
|
51
|
+
return handleResponse<EntityDTO>(res);
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
list: async (params = {}) => {
|
|
55
|
+
const searchParams = new URLSearchParams();
|
|
56
|
+
if (params.limit) searchParams.set("limit", String(params.limit));
|
|
57
|
+
if (params.offset) searchParams.set("offset", String(params.offset));
|
|
58
|
+
|
|
59
|
+
const url = searchParams.toString() ? `${BASE_URL}?${searchParams}` : BASE_URL;
|
|
60
|
+
const res = await fetch(url);
|
|
61
|
+
return handleResponse<EntityDTO[]>(res);
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
create: async (input) => {
|
|
65
|
+
const res = await fetch(BASE_URL, {
|
|
66
|
+
method: "POST",
|
|
67
|
+
headers: { "Content-Type": "application/json" },
|
|
68
|
+
body: JSON.stringify(input),
|
|
69
|
+
});
|
|
70
|
+
return handleResponse<EntityDTO>(res);
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
update: async (id, input) => {
|
|
74
|
+
const res = await fetch(`${BASE_URL}/${id}`, {
|
|
75
|
+
method: "PATCH",
|
|
76
|
+
headers: { "Content-Type": "application/json" },
|
|
77
|
+
body: JSON.stringify(input),
|
|
78
|
+
});
|
|
79
|
+
return handleResponse<EntityDTO>(res);
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
delete: async (id) => {
|
|
83
|
+
const res = await fetch(`${BASE_URL}/${id}`, { method: "DELETE" });
|
|
84
|
+
if (!res.ok) throw new Error(`Failed to delete ${id}`);
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Structure
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
services/
|
|
94
|
+
└── [name]/
|
|
95
|
+
├── [name].service.ts # Service definition
|
|
96
|
+
└── index.ts # Export
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Checklist
|
|
100
|
+
|
|
101
|
+
| Item | Required |
|
|
102
|
+
| --------------------- | -------- |
|
|
103
|
+
| Use `define()` | ✅ |
|
|
104
|
+
| Return type interface | ✅ |
|
|
105
|
+
| Pure functions only | ✅ |
|
|
106
|
+
| NO atoms | ✅ |
|
|
107
|
+
| NO side effects | ✅ |
|
|
108
|
+
| Error handling | ✅ |
|
|
109
|
+
|
|
110
|
+
## Naming
|
|
111
|
+
|
|
112
|
+
| Type | Pattern | Example |
|
|
113
|
+
| ------- | -------------------- | -------------------- |
|
|
114
|
+
| Service | `[name]Service` | `entityService` |
|
|
115
|
+
| File | `[name].service.ts` | `entity.service.ts` |
|
|
116
|
+
| Methods | verb-led | `fetch`, `create` |
|
|
117
|
+
|
|
118
|
+
## Platform Override
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
// services/storage/storage.service.ts
|
|
122
|
+
export interface StorageService {
|
|
123
|
+
get: (key: string) => Promise<string | null>;
|
|
124
|
+
set: (key: string, value: string) => Promise<void>;
|
|
125
|
+
remove: (key: string) => Promise<void>;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export const storageService = define((): StorageService => {
|
|
129
|
+
throw new Error("StorageService not implemented. Override for platform.");
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// services/storage/storage.web.ts
|
|
133
|
+
export const webStorageService = define((): StorageService => ({
|
|
134
|
+
get: async (key) => localStorage.getItem(key),
|
|
135
|
+
set: async (key, value) => localStorage.setItem(key, value),
|
|
136
|
+
remove: async (key) => localStorage.removeItem(key),
|
|
137
|
+
}));
|
|
138
|
+
|
|
139
|
+
// services/storage/storage.native.ts
|
|
140
|
+
import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
141
|
+
|
|
142
|
+
export const nativeStorageService = define((): StorageService => ({
|
|
143
|
+
get: (key) => AsyncStorage.getItem(key),
|
|
144
|
+
set: (key, value) => AsyncStorage.setItem(key, value),
|
|
145
|
+
remove: (key) => AsyncStorage.removeItem(key),
|
|
146
|
+
}));
|
|
147
|
+
|
|
148
|
+
// app/init.ts
|
|
149
|
+
import { storageService } from "@/services/storage/storage.service";
|
|
150
|
+
import { webStorageService } from "@/services/storage/storage.web";
|
|
151
|
+
|
|
152
|
+
storageService.override(webStorageService);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Usage in Store
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
import { entityService } from "@/services/entity/entity.service";
|
|
159
|
+
|
|
160
|
+
export const entityStore = define(() => {
|
|
161
|
+
const api = entityService(); // Inject via invocation
|
|
162
|
+
|
|
163
|
+
const entities$ = atom<EntityDTO[]>([], { meta: { key: "entity.list" } });
|
|
164
|
+
|
|
165
|
+
const loadEntities = async () => {
|
|
166
|
+
const data = await api.list();
|
|
167
|
+
entities$.set(data);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
return { ...readonly({ entities$ }), loadEntities };
|
|
171
|
+
});
|
|
172
|
+
```
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Store Template
|
|
2
|
+
|
|
3
|
+
Stores = stateful modules with atoms. Use `define()`.
|
|
4
|
+
|
|
5
|
+
## Template
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
// features/[feature]/stores/[name].store.ts
|
|
9
|
+
import { atom, derived, effect, readonly, batch, define } from "atomirx";
|
|
10
|
+
import { someService } from "@/services/some/some.service";
|
|
11
|
+
|
|
12
|
+
// ==================== Types ====================
|
|
13
|
+
|
|
14
|
+
interface EntityState {
|
|
15
|
+
id: string;
|
|
16
|
+
name: string;
|
|
17
|
+
status: "idle" | "loading" | "error";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ==================== Store ====================
|
|
21
|
+
|
|
22
|
+
export const entityStore = define(() => {
|
|
23
|
+
// ---------- Dependencies ----------
|
|
24
|
+
const api = someService();
|
|
25
|
+
|
|
26
|
+
// ---------- State ----------
|
|
27
|
+
const entities$ = atom<Map<string, EntityState>>(new Map(), {
|
|
28
|
+
meta: { key: "entity.entities" },
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const currentId$ = atom<string | undefined>(undefined, {
|
|
32
|
+
meta: { key: "entity.currentId" },
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const filter$ = atom("", {
|
|
36
|
+
meta: { key: "entity.filter" },
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// ---------- Derived ----------
|
|
40
|
+
const currentEntity$ = derived(
|
|
41
|
+
({ read, ready }) => {
|
|
42
|
+
const id = ready(currentId$);
|
|
43
|
+
return read(entities$).get(id) ?? null;
|
|
44
|
+
},
|
|
45
|
+
{ meta: { key: "entity.current" }, fallback: null }
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const filteredEntities$ = derived(
|
|
49
|
+
({ read }) => {
|
|
50
|
+
const entities = read(entities$);
|
|
51
|
+
const filter = read(filter$).toLowerCase();
|
|
52
|
+
if (!filter) return [...entities.values()];
|
|
53
|
+
return [...entities.values()].filter((e) =>
|
|
54
|
+
e.name.toLowerCase().includes(filter)
|
|
55
|
+
);
|
|
56
|
+
},
|
|
57
|
+
{ meta: { key: "entity.filtered" } }
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// ---------- Effects ----------
|
|
61
|
+
effect(
|
|
62
|
+
({ read }) => {
|
|
63
|
+
const current = read(currentEntity$);
|
|
64
|
+
if (current) localStorage.setItem("lastEntity", current.id);
|
|
65
|
+
},
|
|
66
|
+
{ meta: { key: "entity.persistLast" } }
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
// ---------- Actions ----------
|
|
70
|
+
const setFilter = (value: string) => filter$.set(value);
|
|
71
|
+
|
|
72
|
+
const selectEntity = (id: string | undefined) => currentId$.set(id);
|
|
73
|
+
|
|
74
|
+
const fetchEntity = async (id: string) => {
|
|
75
|
+
batch(() => {
|
|
76
|
+
entities$.set((prev) => {
|
|
77
|
+
const next = new Map(prev);
|
|
78
|
+
const existing = next.get(id);
|
|
79
|
+
next.set(id, { ...(existing ?? { id, name: "" }), status: "loading" });
|
|
80
|
+
return next;
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const data = await api.fetch(id);
|
|
86
|
+
entities$.set((prev) => {
|
|
87
|
+
const next = new Map(prev);
|
|
88
|
+
next.set(id, { ...data, status: "idle" });
|
|
89
|
+
return next;
|
|
90
|
+
});
|
|
91
|
+
return data;
|
|
92
|
+
} catch (error) {
|
|
93
|
+
entities$.set((prev) => {
|
|
94
|
+
const next = new Map(prev);
|
|
95
|
+
const existing = next.get(id);
|
|
96
|
+
if (existing) next.set(id, { ...existing, status: "error" });
|
|
97
|
+
return next;
|
|
98
|
+
});
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const updateEntity = async (id: string, updates: Partial<EntityState>) => {
|
|
104
|
+
const prev = entities$.get().get(id);
|
|
105
|
+
if (!prev) throw new Error(`Entity ${id} not found`);
|
|
106
|
+
|
|
107
|
+
// Optimistic
|
|
108
|
+
entities$.set((map) => {
|
|
109
|
+
const next = new Map(map);
|
|
110
|
+
next.set(id, { ...prev, ...updates });
|
|
111
|
+
return next;
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
await api.update(id, updates);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
// Rollback
|
|
118
|
+
entities$.set((map) => {
|
|
119
|
+
const next = new Map(map);
|
|
120
|
+
next.set(id, prev);
|
|
121
|
+
return next;
|
|
122
|
+
});
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const reset = () => {
|
|
128
|
+
batch(() => {
|
|
129
|
+
entities$.reset();
|
|
130
|
+
currentId$.reset();
|
|
131
|
+
filter$.reset();
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// ---------- Return ----------
|
|
136
|
+
return {
|
|
137
|
+
// Read-only state
|
|
138
|
+
...readonly({ entities$, currentId$, filter$, currentEntity$, filteredEntities$ }),
|
|
139
|
+
|
|
140
|
+
// Actions
|
|
141
|
+
setFilter,
|
|
142
|
+
selectEntity,
|
|
143
|
+
fetchEntity,
|
|
144
|
+
updateEntity,
|
|
145
|
+
reset,
|
|
146
|
+
};
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Structure
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
features/
|
|
154
|
+
└── [feature]/
|
|
155
|
+
└── stores/
|
|
156
|
+
├── [name].store.ts # Store definition
|
|
157
|
+
└── index.ts # Export
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Checklist
|
|
161
|
+
|
|
162
|
+
| Item | Required |
|
|
163
|
+
| ------------------------ | -------- |
|
|
164
|
+
| Use `define()` | ✅ |
|
|
165
|
+
| `meta.key` on ALL atoms | ✅ |
|
|
166
|
+
| `meta.key` on derived | ✅ |
|
|
167
|
+
| `meta.key` on effects | ✅ |
|
|
168
|
+
| `readonly()` for state | ✅ |
|
|
169
|
+
| Actions return Promises | When async |
|
|
170
|
+
| Use `batch()` multi-update | ✅ |
|
|
171
|
+
| Inject services via call | ✅ |
|
|
172
|
+
| Optimistic + rollback | For UX |
|
|
173
|
+
|
|
174
|
+
## Naming
|
|
175
|
+
|
|
176
|
+
| Type | Pattern | Example |
|
|
177
|
+
| --------- | ---------------- | ---------------------- |
|
|
178
|
+
| Store | `[name]Store` | `entityStore` |
|
|
179
|
+
| File | `[name].store.ts`| `entity.store.ts` |
|
|
180
|
+
| Atoms | `[name]$` | `entities$`, `filter$` |
|
|
181
|
+
| Derived | `[computed]$` | `currentEntity$` |
|
|
182
|
+
| Actions | verb-led | `fetchEntity`, `reset` |
|
|
183
|
+
|
|
184
|
+
## Usage
|
|
185
|
+
|
|
186
|
+
```tsx
|
|
187
|
+
function EntityList() {
|
|
188
|
+
const store = entityStore();
|
|
189
|
+
|
|
190
|
+
const entities = useSelector(({ read }) => read(store.filteredEntities$));
|
|
191
|
+
const stable = useStable({
|
|
192
|
+
onSelect: (id: string) => store.selectEntity(id),
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
<ul>
|
|
197
|
+
{entities.map((e) => (
|
|
198
|
+
<li key={e.id} onClick={() => stable.onSelect(e.id)}>
|
|
199
|
+
{e.name}
|
|
200
|
+
</li>
|
|
201
|
+
))}
|
|
202
|
+
</ul>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
```
|