@livestore/react 0.0.0-snapshot-8d3edf87cb1e88c7b67c5f3ea9d0b307253c33df

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.
Files changed (87) hide show
  1. package/README.md +1 -0
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/LiveStoreContext.d.ts +7 -0
  4. package/dist/LiveStoreContext.d.ts.map +1 -0
  5. package/dist/LiveStoreContext.js +13 -0
  6. package/dist/LiveStoreContext.js.map +1 -0
  7. package/dist/LiveStoreProvider.d.ts +49 -0
  8. package/dist/LiveStoreProvider.d.ts.map +1 -0
  9. package/dist/LiveStoreProvider.js +168 -0
  10. package/dist/LiveStoreProvider.js.map +1 -0
  11. package/dist/LiveStoreProvider.test.d.ts +2 -0
  12. package/dist/LiveStoreProvider.test.d.ts.map +1 -0
  13. package/dist/LiveStoreProvider.test.js +62 -0
  14. package/dist/LiveStoreProvider.test.js.map +1 -0
  15. package/dist/__tests__/fixture.d.ts +567 -0
  16. package/dist/__tests__/fixture.d.ts.map +1 -0
  17. package/dist/__tests__/fixture.js +61 -0
  18. package/dist/__tests__/fixture.js.map +1 -0
  19. package/dist/experimental/components/LiveList.d.ts +21 -0
  20. package/dist/experimental/components/LiveList.d.ts.map +1 -0
  21. package/dist/experimental/components/LiveList.js +31 -0
  22. package/dist/experimental/components/LiveList.js.map +1 -0
  23. package/dist/experimental/mod.d.ts +2 -0
  24. package/dist/experimental/mod.d.ts.map +1 -0
  25. package/dist/experimental/mod.js +2 -0
  26. package/dist/experimental/mod.js.map +1 -0
  27. package/dist/mod.d.ts +8 -0
  28. package/dist/mod.d.ts.map +1 -0
  29. package/dist/mod.js +8 -0
  30. package/dist/mod.js.map +1 -0
  31. package/dist/useAtom.d.ts +10 -0
  32. package/dist/useAtom.d.ts.map +1 -0
  33. package/dist/useAtom.js +37 -0
  34. package/dist/useAtom.js.map +1 -0
  35. package/dist/useQuery.d.ts +9 -0
  36. package/dist/useQuery.d.ts.map +1 -0
  37. package/dist/useQuery.js +88 -0
  38. package/dist/useQuery.js.map +1 -0
  39. package/dist/useQuery.test.d.ts +2 -0
  40. package/dist/useQuery.test.d.ts.map +1 -0
  41. package/dist/useQuery.test.js +51 -0
  42. package/dist/useQuery.test.js.map +1 -0
  43. package/dist/useRow.d.ts +46 -0
  44. package/dist/useRow.d.ts.map +1 -0
  45. package/dist/useRow.js +96 -0
  46. package/dist/useRow.js.map +1 -0
  47. package/dist/useRow.test.d.ts +2 -0
  48. package/dist/useRow.test.d.ts.map +1 -0
  49. package/dist/useRow.test.js +212 -0
  50. package/dist/useRow.test.js.map +1 -0
  51. package/dist/useTemporaryQuery.d.ts +22 -0
  52. package/dist/useTemporaryQuery.d.ts.map +1 -0
  53. package/dist/useTemporaryQuery.js +75 -0
  54. package/dist/useTemporaryQuery.js.map +1 -0
  55. package/dist/useTemporaryQuery.test.d.ts +2 -0
  56. package/dist/useTemporaryQuery.test.d.ts.map +1 -0
  57. package/dist/useTemporaryQuery.test.js +59 -0
  58. package/dist/useTemporaryQuery.test.js.map +1 -0
  59. package/dist/utils/stack-info.d.ts +4 -0
  60. package/dist/utils/stack-info.d.ts.map +1 -0
  61. package/dist/utils/stack-info.js +11 -0
  62. package/dist/utils/stack-info.js.map +1 -0
  63. package/dist/utils/useStateRefWithReactiveInput.d.ts +13 -0
  64. package/dist/utils/useStateRefWithReactiveInput.d.ts.map +1 -0
  65. package/dist/utils/useStateRefWithReactiveInput.js +38 -0
  66. package/dist/utils/useStateRefWithReactiveInput.js.map +1 -0
  67. package/package.json +54 -0
  68. package/src/LiveStoreContext.ts +19 -0
  69. package/src/LiveStoreProvider.test.tsx +109 -0
  70. package/src/LiveStoreProvider.tsx +295 -0
  71. package/src/__snapshots__/useRow.test.tsx.snap +359 -0
  72. package/src/__tests__/fixture.tsx +119 -0
  73. package/src/ambient.d.ts +2 -0
  74. package/src/experimental/components/LiveList.tsx +84 -0
  75. package/src/experimental/mod.ts +1 -0
  76. package/src/mod.ts +13 -0
  77. package/src/useAtom.ts +55 -0
  78. package/src/useQuery.test.tsx +82 -0
  79. package/src/useQuery.ts +122 -0
  80. package/src/useRow.test.tsx +346 -0
  81. package/src/useRow.ts +182 -0
  82. package/src/useTemporaryQuery.test.tsx +98 -0
  83. package/src/useTemporaryQuery.ts +131 -0
  84. package/src/utils/stack-info.ts +13 -0
  85. package/src/utils/useStateRefWithReactiveInput.ts +51 -0
  86. package/tsconfig.json +20 -0
  87. package/vitest.config.js +17 -0
@@ -0,0 +1,359 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`useRow > otel > should update the data based on component key > strictMode=false 1`] = `
4
+ {
5
+ "_name": "test",
6
+ "children": [
7
+ {
8
+ "_name": "livestore.in-memory-db:execute",
9
+ "attributes": {
10
+ "sql.query": "
11
+ PRAGMA page_size=32768;
12
+ PRAGMA cache_size=10000;
13
+ PRAGMA journal_mode='MEMORY'; -- we don't flush to disk before committing a write
14
+ PRAGMA synchronous='OFF';
15
+ PRAGMA temp_store='MEMORY';
16
+ PRAGMA foreign_keys='ON'; -- we want foreign key constraints to be enforced
17
+ ",
18
+ },
19
+ },
20
+ {
21
+ "_name": "sql-in-memory-select",
22
+ "attributes": {
23
+ "sql.cached": false,
24
+ "sql.query": "select 1 from UserInfo where id = 'u1'",
25
+ "sql.rowsCount": 0,
26
+ },
27
+ },
28
+ {
29
+ "_name": "sql-in-memory-select",
30
+ "attributes": {
31
+ "sql.cached": false,
32
+ "sql.query": "select 1 from UserInfo where id = 'u2'",
33
+ "sql.rowsCount": 1,
34
+ },
35
+ },
36
+ {
37
+ "_name": "LiveStore:mutations",
38
+ "children": [
39
+ {
40
+ "_name": "LiveStore:mutate",
41
+ "attributes": {
42
+ "livestore.mutateLabel": "mutate",
43
+ },
44
+ "children": [
45
+ {
46
+ "_name": "LiveStore:processWrites",
47
+ "attributes": {
48
+ "livestore.mutateLabel": "mutate",
49
+ },
50
+ "children": [
51
+ {
52
+ "_name": "LiveStore:mutateWithoutRefresh",
53
+ "attributes": {
54
+ "livestore.args": "{
55
+ "sql": "INSERT INTO UserInfo (id, username) VALUES ('u2', 'username_u2')"
56
+ }",
57
+ "livestore.mutation": "livestore.RawSql",
58
+ },
59
+ "children": [
60
+ {
61
+ "_name": "livestore.in-memory-db:execute",
62
+ "attributes": {
63
+ "sql.query": "INSERT INTO UserInfo (id, username) VALUES ('u2', 'username_u2')",
64
+ },
65
+ },
66
+ ],
67
+ },
68
+ ],
69
+ },
70
+ ],
71
+ },
72
+ ],
73
+ },
74
+ {
75
+ "_name": "LiveStore:queries",
76
+ "children": [
77
+ {
78
+ "_name": "sql:select * from UserInfo where id = 'u1' limit 1",
79
+ "attributes": {
80
+ "sql.query": "select * from UserInfo where id = 'u1' limit 1",
81
+ "sql.rowsCount": 1,
82
+ },
83
+ "children": [
84
+ {
85
+ "_name": "sql-in-memory-select",
86
+ "attributes": {
87
+ "sql.cached": false,
88
+ "sql.query": "select * from UserInfo where id = 'u1' limit 1",
89
+ "sql.rowsCount": 1,
90
+ },
91
+ },
92
+ ],
93
+ },
94
+ {
95
+ "_name": "LiveStore:useRow:UserInfo:u1",
96
+ "attributes": {
97
+ "id": "u1",
98
+ },
99
+ "children": [
100
+ {
101
+ "_name": "LiveStore:mutateWithoutRefresh",
102
+ "attributes": {
103
+ "livestore.args": "{
104
+ "id": "u1"
105
+ }",
106
+ "livestore.mutation": "_Derived_Create_UserInfo",
107
+ },
108
+ "children": [
109
+ {
110
+ "_name": "livestore.in-memory-db:execute",
111
+ "attributes": {
112
+ "sql.query": "INSERT INTO UserInfo (username, text, id) VALUES ($username, $text, $id)",
113
+ },
114
+ },
115
+ ],
116
+ },
117
+ {
118
+ "_name": "LiveStore:useQuery:sql(rowQuery:query:UserInfo:u1)",
119
+ "attributes": {
120
+ "label": "sql(rowQuery:query:UserInfo:u1)",
121
+ "stackInfo": "{"frames":[{"name":"renderHook.wrapper","filePath":"__REPLACED_FOR_SNAPSHOT__"},{"name":"useRow","filePath":"__REPLACED_FOR_SNAPSHOT__"}]}",
122
+ },
123
+ "children": [
124
+ {
125
+ "_name": "sql:select * from UserInfo where id = 'u1' limit 1",
126
+ "attributes": {
127
+ "sql.query": "select * from UserInfo where id = 'u1' limit 1",
128
+ "sql.rowsCount": 1,
129
+ },
130
+ "children": [
131
+ {
132
+ "_name": "sql-in-memory-select",
133
+ "attributes": {
134
+ "sql.cached": false,
135
+ "sql.query": "select * from UserInfo where id = 'u1' limit 1",
136
+ "sql.rowsCount": 1,
137
+ },
138
+ },
139
+ ],
140
+ },
141
+ {
142
+ "_name": "LiveStore.subscribe",
143
+ "attributes": {
144
+ "label": "sql(rowQuery:query:UserInfo:u1)",
145
+ "queryLabel": "sql(rowQuery:query:UserInfo:u1)",
146
+ },
147
+ },
148
+ ],
149
+ },
150
+ ],
151
+ },
152
+ {
153
+ "_name": "LiveStore:useRow:UserInfo:u2",
154
+ "attributes": {
155
+ "id": "u2",
156
+ },
157
+ "children": [
158
+ {
159
+ "_name": "LiveStore:useQuery:sql(rowQuery:query:UserInfo:u2)",
160
+ "attributes": {
161
+ "label": "sql(rowQuery:query:UserInfo:u2)",
162
+ "stackInfo": "{"frames":[{"name":"renderHook.wrapper","filePath":"__REPLACED_FOR_SNAPSHOT__"},{"name":"useRow","filePath":"__REPLACED_FOR_SNAPSHOT__"}]}",
163
+ },
164
+ "children": [
165
+ {
166
+ "_name": "sql:select * from UserInfo where id = 'u2' limit 1",
167
+ "attributes": {
168
+ "sql.query": "select * from UserInfo where id = 'u2' limit 1",
169
+ "sql.rowsCount": 1,
170
+ },
171
+ "children": [
172
+ {
173
+ "_name": "sql-in-memory-select",
174
+ "attributes": {
175
+ "sql.cached": false,
176
+ "sql.query": "select * from UserInfo where id = 'u2' limit 1",
177
+ "sql.rowsCount": 1,
178
+ },
179
+ },
180
+ ],
181
+ },
182
+ {
183
+ "_name": "LiveStore.subscribe",
184
+ "attributes": {
185
+ "label": "sql(rowQuery:query:UserInfo:u2)",
186
+ "queryLabel": "sql(rowQuery:query:UserInfo:u2)",
187
+ },
188
+ },
189
+ ],
190
+ },
191
+ ],
192
+ },
193
+ ],
194
+ },
195
+ ],
196
+ }
197
+ `;
198
+
199
+ exports[`useRow > otel > should update the data based on component key > strictMode=true 1`] = `
200
+ {
201
+ "_name": "test",
202
+ "children": [
203
+ {
204
+ "_name": "livestore.in-memory-db:execute",
205
+ "attributes": {
206
+ "sql.query": "
207
+ PRAGMA page_size=32768;
208
+ PRAGMA cache_size=10000;
209
+ PRAGMA journal_mode='MEMORY'; -- we don't flush to disk before committing a write
210
+ PRAGMA synchronous='OFF';
211
+ PRAGMA temp_store='MEMORY';
212
+ PRAGMA foreign_keys='ON'; -- we want foreign key constraints to be enforced
213
+ ",
214
+ },
215
+ },
216
+ {
217
+ "_name": "sql-in-memory-select",
218
+ "attributes": {
219
+ "sql.cached": false,
220
+ "sql.query": "select 1 from UserInfo where id = 'u1'",
221
+ "sql.rowsCount": 0,
222
+ },
223
+ },
224
+ {
225
+ "_name": "sql-in-memory-select",
226
+ "attributes": {
227
+ "sql.cached": false,
228
+ "sql.query": "select 1 from UserInfo where id = 'u2'",
229
+ "sql.rowsCount": 1,
230
+ },
231
+ },
232
+ {
233
+ "_name": "LiveStore:mutations",
234
+ "children": [
235
+ {
236
+ "_name": "LiveStore:mutate",
237
+ "attributes": {
238
+ "livestore.mutateLabel": "mutate",
239
+ },
240
+ "children": [
241
+ {
242
+ "_name": "LiveStore:processWrites",
243
+ "attributes": {
244
+ "livestore.mutateLabel": "mutate",
245
+ },
246
+ "children": [
247
+ {
248
+ "_name": "LiveStore:mutateWithoutRefresh",
249
+ "attributes": {
250
+ "livestore.args": "{
251
+ "sql": "INSERT INTO UserInfo (id, username) VALUES ('u2', 'username_u2')"
252
+ }",
253
+ "livestore.mutation": "livestore.RawSql",
254
+ },
255
+ "children": [
256
+ {
257
+ "_name": "livestore.in-memory-db:execute",
258
+ "attributes": {
259
+ "sql.query": "INSERT INTO UserInfo (id, username) VALUES ('u2', 'username_u2')",
260
+ },
261
+ },
262
+ ],
263
+ },
264
+ ],
265
+ },
266
+ ],
267
+ },
268
+ ],
269
+ },
270
+ {
271
+ "_name": "LiveStore:queries",
272
+ "children": [
273
+ {
274
+ "_name": "sql:select * from UserInfo where id = 'u1' limit 1",
275
+ "attributes": {
276
+ "sql.query": "select * from UserInfo where id = 'u1' limit 1",
277
+ "sql.rowsCount": 1,
278
+ },
279
+ "children": [
280
+ {
281
+ "_name": "sql-in-memory-select",
282
+ "attributes": {
283
+ "sql.cached": false,
284
+ "sql.query": "select * from UserInfo where id = 'u1' limit 1",
285
+ "sql.rowsCount": 1,
286
+ },
287
+ },
288
+ ],
289
+ },
290
+ {
291
+ "_name": "LiveStore:useRow:UserInfo:u1",
292
+ "attributes": {
293
+ "id": "u1",
294
+ },
295
+ "children": [
296
+ {
297
+ "_name": "LiveStore:mutateWithoutRefresh",
298
+ "attributes": {
299
+ "livestore.args": "{
300
+ "id": "u1"
301
+ }",
302
+ "livestore.mutation": "_Derived_Create_UserInfo",
303
+ },
304
+ "children": [
305
+ {
306
+ "_name": "livestore.in-memory-db:execute",
307
+ "attributes": {
308
+ "sql.query": "INSERT INTO UserInfo (username, text, id) VALUES ($username, $text, $id)",
309
+ },
310
+ },
311
+ ],
312
+ },
313
+ {
314
+ "_name": "LiveStore:useQuery:sql(rowQuery:query:UserInfo:u1)",
315
+ "attributes": {
316
+ "label": "sql(rowQuery:query:UserInfo:u1)",
317
+ "stackInfo": "{"frames":[{"name":"renderHook.wrapper","filePath":"__REPLACED_FOR_SNAPSHOT__"},{"name":"useRow","filePath":"__REPLACED_FOR_SNAPSHOT__"}]}",
318
+ },
319
+ "children": [
320
+ {
321
+ "_name": "sql:select * from UserInfo where id = 'u1' limit 1",
322
+ "attributes": {
323
+ "sql.query": "select * from UserInfo where id = 'u1' limit 1",
324
+ "sql.rowsCount": 1,
325
+ },
326
+ "children": [
327
+ {
328
+ "_name": "sql-in-memory-select",
329
+ "attributes": {
330
+ "sql.cached": false,
331
+ "sql.query": "select * from UserInfo where id = 'u1' limit 1",
332
+ "sql.rowsCount": 1,
333
+ },
334
+ },
335
+ ],
336
+ },
337
+ {
338
+ "_name": "LiveStore.subscribe",
339
+ "attributes": {
340
+ "label": "sql(rowQuery:query:UserInfo:u1)",
341
+ "queryLabel": "sql(rowQuery:query:UserInfo:u1)",
342
+ },
343
+ },
344
+ {
345
+ "_name": "LiveStore.subscribe",
346
+ "attributes": {
347
+ "label": "sql(rowQuery:query:UserInfo:u1)",
348
+ "queryLabel": "sql(rowQuery:query:UserInfo:u1)",
349
+ },
350
+ },
351
+ ],
352
+ },
353
+ ],
354
+ },
355
+ ],
356
+ },
357
+ ],
358
+ }
359
+ `;
@@ -0,0 +1,119 @@
1
+ import { sql } from '@livestore/common'
2
+ import { DbSchema, makeSchema } from '@livestore/common/schema'
3
+ import type { LiveStoreContextRunning } from '@livestore/livestore'
4
+ import { createStore, globalReactivityGraph, makeReactivityGraph } from '@livestore/livestore'
5
+ import { Effect, FiberSet } from '@livestore/utils/effect'
6
+ import { makeInMemoryAdapter } from '@livestore/web'
7
+ import type * as otel from '@opentelemetry/api'
8
+ import React from 'react'
9
+
10
+ import * as LiveStoreReact from '../mod.js'
11
+
12
+ export type Todo = {
13
+ id: string
14
+ text: string
15
+ completed: boolean
16
+ }
17
+
18
+ export type Filter = 'all' | 'active' | 'completed'
19
+
20
+ export type AppState = {
21
+ newTodoText: string
22
+ filter: Filter
23
+ }
24
+
25
+ export const todos = DbSchema.table(
26
+ 'todos',
27
+ {
28
+ id: DbSchema.text({ primaryKey: true }),
29
+ text: DbSchema.text({ default: '', nullable: false }),
30
+ completed: DbSchema.boolean({ default: false, nullable: false }),
31
+ },
32
+ { deriveMutations: true, isSingleton: false },
33
+ )
34
+
35
+ export const app = DbSchema.table('app', {
36
+ id: DbSchema.text({ primaryKey: true }),
37
+ newTodoText: DbSchema.text({ default: '', nullable: true }),
38
+ filter: DbSchema.text({ default: 'all', nullable: false }),
39
+ })
40
+
41
+ export const AppComponentSchema = DbSchema.table(
42
+ 'UserInfo',
43
+ {
44
+ username: DbSchema.text({ default: '' }),
45
+ text: DbSchema.text({ default: '' }),
46
+ },
47
+ { deriveMutations: true },
48
+ )
49
+
50
+ export const AppRouterSchema = DbSchema.table(
51
+ 'AppRouter',
52
+ {
53
+ currentTaskId: DbSchema.text({ default: null, nullable: true }),
54
+ },
55
+ { isSingleton: true, deriveMutations: true },
56
+ )
57
+
58
+ export const tables = { todos, app, AppComponentSchema, AppRouterSchema }
59
+ export const schema = makeSchema({ tables })
60
+
61
+ export const makeTodoMvcReact = ({
62
+ otelTracer,
63
+ otelContext,
64
+ useGlobalReactivityGraph = true,
65
+ strictMode = process.env.REACT_STRICT_MODE !== undefined,
66
+ }: {
67
+ otelTracer?: otel.Tracer
68
+ otelContext?: otel.Context
69
+ useGlobalReactivityGraph?: boolean
70
+ strictMode?: boolean
71
+ } = {}) =>
72
+ Effect.gen(function* () {
73
+ const makeRenderCount = () => {
74
+ let val = 0
75
+
76
+ const inc = () => {
77
+ val += strictMode ? 0.5 : 1
78
+ }
79
+
80
+ return {
81
+ get val() {
82
+ return val
83
+ },
84
+ inc,
85
+ }
86
+ }
87
+
88
+ const reactivityGraph = useGlobalReactivityGraph ? globalReactivityGraph : makeReactivityGraph()
89
+
90
+ const fiberSet = yield* FiberSet.make()
91
+
92
+ const store = yield* createStore({
93
+ schema,
94
+ storeId: 'default',
95
+ boot: (db) => db.execute(sql`INSERT OR IGNORE INTO app (id, newTodoText, filter) VALUES ('static', '', 'all');`),
96
+ adapter: makeInMemoryAdapter(),
97
+ reactivityGraph,
98
+ otelOptions: {
99
+ tracer: otelTracer,
100
+ rootSpanContext: otelContext,
101
+ },
102
+ fiberSet,
103
+ })
104
+
105
+ // TODO improve typing of `LiveStoreContext`
106
+ const storeContext = { stage: 'running', store } as any as LiveStoreContextRunning
107
+
108
+ const MaybeStrictMode = strictMode ? React.StrictMode : React.Fragment
109
+
110
+ const wrapper = ({ children }: any) => (
111
+ <MaybeStrictMode>
112
+ <LiveStoreReact.LiveStoreContext.Provider value={storeContext}>
113
+ {children}
114
+ </LiveStoreReact.LiveStoreContext.Provider>
115
+ </MaybeStrictMode>
116
+ )
117
+
118
+ return { wrapper, store, reactivityGraph, makeRenderCount, strictMode }
119
+ })
@@ -0,0 +1,2 @@
1
+ // eslint-disable-next-line no-var
2
+ var __debugLiveStore: any
@@ -0,0 +1,84 @@
1
+ import type { LiveQuery } from '@livestore/livestore'
2
+ import { computed } from '@livestore/livestore'
3
+ import React from 'react'
4
+
5
+ import { useQuery } from '../../useQuery.js'
6
+ import { useTemporaryQuery } from '../../useTemporaryQuery.js'
7
+
8
+ /*
9
+ TODO:
10
+ - [ ] Bring back incremental rendering (see https://github.com/livestorejs/livestore/pull/55)
11
+ - [ ] Enable exit animations
12
+ */
13
+
14
+ export type LiveListProps<TItem> = {
15
+ items$: LiveQuery<ReadonlyArray<TItem>>
16
+ // TODO refactor render-flag to allow for transition animations on add/remove
17
+ renderItem: (item: TItem, opts: { index: number; isInitialListRender: boolean }) => React.ReactNode
18
+ /** Needs to be unique across all list items */
19
+ getKey: (item: TItem, index: number) => string | number
20
+ }
21
+
22
+ /**
23
+ * This component is a helper component for rendering a list of items for a LiveQuery of an array of items.
24
+ * The idea is that instead of letting React handle the rendering of the items array directly,
25
+ * we derive a item LiveQuery for each item which moves the reactivity to the item level when a single item changes.
26
+ *
27
+ * In the future we want to make this component even more efficient by using incremental rendering (https://github.com/livestorejs/livestore/pull/55)
28
+ * e.g. when an item is added/removed/moved to only re-render the affected DOM nodes.
29
+ */
30
+ export const LiveList = <TItem,>({ items$, renderItem, getKey }: LiveListProps<TItem>): React.ReactNode => {
31
+ const [hasMounted, setHasMounted] = React.useState(false)
32
+
33
+ React.useEffect(() => setHasMounted(true), [])
34
+
35
+ const keysCb = React.useCallback(() => computed((get) => get(items$).map(getKey)), [getKey, items$])
36
+ const keys = useTemporaryQuery(keysCb, 'fixed')
37
+ const arr = React.useMemo(
38
+ () =>
39
+ keys.map(
40
+ (key) =>
41
+ // TODO figure out a way so that `item$` returns an ordered lookup map to more efficiently find the item by key
42
+ [key, computed((get) => get(items$).find((item) => getKey(item, 0) === key)!) as LiveQuery<TItem>] as const,
43
+ ),
44
+ [getKey, items$, keys],
45
+ )
46
+
47
+ return (
48
+ <>
49
+ {arr.map(([key, item$], index) => (
50
+ <ItemWrapperMemo
51
+ key={key}
52
+ itemKey={key}
53
+ item$={item$}
54
+ opts={{ isInitialListRender: !hasMounted, index }}
55
+ renderItem={renderItem}
56
+ />
57
+ ))}
58
+ </>
59
+ )
60
+ }
61
+
62
+ const ItemWrapper = <TItem,>({
63
+ item$,
64
+ opts,
65
+ renderItem,
66
+ }: {
67
+ itemKey: string | number
68
+ item$: LiveQuery<TItem>
69
+ opts: { index: number; isInitialListRender: boolean }
70
+ renderItem: (item: TItem, opts: { index: number; isInitialListRender: boolean }) => React.ReactNode
71
+ }) => {
72
+ const item = useQuery(item$)
73
+
74
+ return <>{renderItem(item, opts)}</>
75
+ }
76
+
77
+ const ItemWrapperMemo = React.memo(
78
+ ItemWrapper,
79
+ (prev, next) =>
80
+ prev.itemKey === next.itemKey &&
81
+ prev.renderItem === prev.renderItem &&
82
+ prev.opts.index === next.opts.index &&
83
+ prev.opts.isInitialListRender === next.opts.isInitialListRender,
84
+ ) as typeof ItemWrapper
@@ -0,0 +1 @@
1
+ export { LiveList, type LiveListProps } from './components/LiveList.js'
package/src/mod.ts ADDED
@@ -0,0 +1,13 @@
1
+ export { LiveStoreContext, useStore } from './LiveStoreContext.js'
2
+ export { LiveStoreProvider } from './LiveStoreProvider.js'
3
+ export { useQuery } from './useQuery.js'
4
+ export { useTemporaryQuery } from './useTemporaryQuery.js'
5
+ export { useStackInfo } from './utils/stack-info.js'
6
+ export {
7
+ useRow,
8
+ type StateSetters,
9
+ type SetStateAction,
10
+ type Dispatch,
11
+ type UseRowResult as UseStateResult,
12
+ } from './useRow.js'
13
+ export { useAtom } from './useAtom.js'
package/src/useAtom.ts ADDED
@@ -0,0 +1,55 @@
1
+ import { type QueryInfoCol, type QueryInfoRow } from '@livestore/common'
2
+ import type { DbSchema } from '@livestore/common/schema'
3
+ import type { LiveQuery } from '@livestore/livestore'
4
+ import React from 'react'
5
+
6
+ import { useStore } from './LiveStoreContext.js'
7
+ import { useQueryRef } from './useQuery.js'
8
+ import type { Dispatch, SetStateAction } from './useRow.js'
9
+
10
+ export const useAtom = <
11
+ TQuery extends LiveQuery<any, QueryInfoRow<TTableDef> | QueryInfoCol<TTableDef, any>>,
12
+ TTableDef extends DbSchema.TableDef<
13
+ DbSchema.DefaultSqliteTableDefConstrained,
14
+ boolean,
15
+ DbSchema.TableOptions & { deriveMutations: { enabled: true } }
16
+ >,
17
+ >(
18
+ query$: TQuery,
19
+ ): [value: TQuery['__result!'], setValue: Dispatch<SetStateAction<Partial<TQuery['__result!']>>>] => {
20
+ const query$Ref = useQueryRef(query$)
21
+
22
+ const { store } = useStore()
23
+
24
+ // TODO make API equivalent to useRow
25
+ const setValue = React.useMemo<Dispatch<SetStateAction<TQuery['__result!']>>>(() => {
26
+ return (newValueOrFn: any) => {
27
+ const newValue = typeof newValueOrFn === 'function' ? newValueOrFn(query$Ref.current) : newValueOrFn
28
+
29
+ if (query$.queryInfo._tag === 'Row') {
30
+ if (query$.queryInfo.table.options.isSingleton && query$.queryInfo.table.isSingleColumn) {
31
+ store.mutate(query$.queryInfo.table.update(newValue))
32
+ } else if (query$.queryInfo.table.options.isSingleColumn) {
33
+ store.mutate(
34
+ query$.queryInfo.table.update({ where: { id: query$.queryInfo.id }, values: { value: newValue } }),
35
+ )
36
+ } else {
37
+ store.mutate(query$.queryInfo.table.update({ where: { id: query$.queryInfo.id }, values: newValue }))
38
+ }
39
+ } else {
40
+ if (query$.queryInfo.table.options.isSingleton && query$.queryInfo.table.isSingleColumn) {
41
+ store.mutate(query$.queryInfo.table.update({ [query$.queryInfo.column]: newValue }))
42
+ } else {
43
+ store.mutate(
44
+ query$.queryInfo.table.update({
45
+ where: { id: query$.queryInfo.id },
46
+ values: { [query$.queryInfo.column]: newValue },
47
+ }),
48
+ )
49
+ }
50
+ }
51
+ }
52
+ }, [query$.queryInfo, query$Ref, store])
53
+
54
+ return [query$Ref.current, setValue]
55
+ }