@livestore/react 0.0.0-snapshot-d9d66b354a9f4cfae987725d38971992ff14e4ad → 0.0.0-snapshot-1d99fea7d2ce2c7a5d9ed0a3752f8a7bda6bc3db
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/dist/.tsbuildinfo +1 -1
- package/dist/LiveStoreContext.d.ts +5 -3
- package/dist/LiveStoreContext.d.ts.map +1 -1
- package/dist/LiveStoreContext.js +7 -3
- package/dist/LiveStoreContext.js.map +1 -1
- package/dist/LiveStoreProvider.d.ts +6 -4
- package/dist/LiveStoreProvider.d.ts.map +1 -1
- package/dist/LiveStoreProvider.js +47 -45
- package/dist/LiveStoreProvider.js.map +1 -1
- package/dist/LiveStoreProvider.test.js +8 -2
- package/dist/LiveStoreProvider.test.js.map +1 -1
- package/dist/__tests__/fixture.d.ts +7 -10
- package/dist/__tests__/fixture.d.ts.map +1 -1
- package/dist/__tests__/fixture.js +10 -15
- package/dist/__tests__/fixture.js.map +1 -1
- package/dist/experimental/components/LiveList.d.ts +2 -2
- package/dist/experimental/components/LiveList.d.ts.map +1 -1
- package/dist/experimental/components/LiveList.js +5 -4
- package/dist/experimental/components/LiveList.js.map +1 -1
- package/dist/mod.d.ts +0 -1
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +0 -1
- package/dist/mod.js.map +1 -1
- package/dist/useAtom.d.ts +4 -2
- package/dist/useAtom.d.ts.map +1 -1
- package/dist/useAtom.js +32 -28
- package/dist/useAtom.js.map +1 -1
- package/dist/useQuery.d.ts +26 -3
- package/dist/useQuery.d.ts.map +1 -1
- package/dist/useQuery.js +60 -45
- package/dist/useQuery.js.map +1 -1
- package/dist/useQuery.test.js +70 -16
- package/dist/useQuery.test.js.map +1 -1
- package/dist/useRcResource.d.ts +76 -0
- package/dist/useRcResource.d.ts.map +1 -0
- package/dist/useRcResource.js +150 -0
- package/dist/useRcResource.js.map +1 -0
- package/dist/useRcResource.test.d.ts +2 -0
- package/dist/useRcResource.test.d.ts.map +1 -0
- package/dist/useRcResource.test.js +122 -0
- package/dist/useRcResource.test.js.map +1 -0
- package/dist/useRow.d.ts +10 -7
- package/dist/useRow.d.ts.map +1 -1
- package/dist/useRow.js +16 -19
- package/dist/useRow.js.map +1 -1
- package/dist/useRow.test.js +74 -97
- package/dist/useRow.test.js.map +1 -1
- package/dist/utils/useStateRefWithReactiveInput.d.ts +1 -1
- package/dist/utils/useStateRefWithReactiveInput.d.ts.map +1 -1
- package/dist/utils/useStateRefWithReactiveInput.js.map +1 -1
- package/package.json +18 -17
- package/src/LiveStoreContext.ts +10 -6
- package/src/LiveStoreProvider.test.tsx +13 -2
- package/src/LiveStoreProvider.tsx +69 -53
- package/src/__snapshots__/useQuery.test.tsx.snap +2011 -0
- package/src/__snapshots__/useRow.test.tsx.snap +347 -153
- package/src/__tests__/fixture.tsx +11 -19
- package/src/experimental/components/LiveList.tsx +8 -7
- package/src/mod.ts +0 -1
- package/src/useAtom.ts +22 -11
- package/src/useQuery.test.tsx +165 -67
- package/src/useQuery.ts +84 -54
- package/src/useRcResource.test.tsx +167 -0
- package/src/useRcResource.ts +180 -0
- package/src/useRow.test.tsx +130 -164
- package/src/useRow.ts +32 -35
- package/src/utils/useStateRefWithReactiveInput.ts +1 -1
- package/dist/useScopedQuery.d.ts +0 -33
- package/dist/useScopedQuery.d.ts.map +0 -1
- package/dist/useScopedQuery.js +0 -86
- package/dist/useScopedQuery.js.map +0 -1
- package/dist/useScopedQuery.test.d.ts +0 -2
- package/dist/useScopedQuery.test.d.ts.map +0 -1
- package/dist/useScopedQuery.test.js +0 -60
- package/dist/useScopedQuery.test.js.map +0 -1
- package/src/useScopedQuery.test.tsx +0 -96
- package/src/useScopedQuery.ts +0 -142
package/src/useRow.test.tsx
CHANGED
|
@@ -1,30 +1,34 @@
|
|
|
1
1
|
import * as LiveStore from '@livestore/livestore'
|
|
2
2
|
import { getSimplifiedRootSpan } from '@livestore/livestore/internal/testing-utils'
|
|
3
3
|
import { Effect, ReadonlyRecord, Schema } from '@livestore/utils/effect'
|
|
4
|
+
import { Vitest } from '@livestore/utils/node-vitest'
|
|
4
5
|
import * as otel from '@opentelemetry/api'
|
|
5
6
|
import { BasicTracerProvider, InMemorySpanExporter, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'
|
|
6
|
-
import
|
|
7
|
+
import * as ReactTesting from '@testing-library/react'
|
|
7
8
|
import React from 'react'
|
|
8
|
-
import {
|
|
9
|
+
import { beforeEach, expect, it } from 'vitest'
|
|
9
10
|
|
|
10
|
-
import {
|
|
11
|
+
import { AppRouterSchema, makeTodoMvcReact, tables, todos } from './__tests__/fixture.js'
|
|
11
12
|
import * as LiveStoreReact from './mod.js'
|
|
13
|
+
import { __resetUseRcResourceCache } from './useRcResource.js'
|
|
14
|
+
|
|
15
|
+
// const strictMode = process.env.REACT_STRICT_MODE !== undefined
|
|
12
16
|
|
|
13
17
|
// NOTE running tests concurrently doesn't work with the default global db graph
|
|
14
|
-
describe('useRow', () => {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
useGlobalReactivityGraph: false,
|
|
19
|
-
})
|
|
18
|
+
Vitest.describe('useRow', () => {
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
__resetUseRcResourceCache()
|
|
21
|
+
})
|
|
20
22
|
|
|
21
|
-
|
|
23
|
+
Vitest.scopedLive('should update the data based on component key', () =>
|
|
24
|
+
Effect.gen(function* () {
|
|
25
|
+
const { wrapper, store, renderCount } = yield* makeTodoMvcReact({})
|
|
22
26
|
|
|
23
|
-
const { result, rerender } = renderHook(
|
|
27
|
+
const { result, rerender } = ReactTesting.renderHook(
|
|
24
28
|
(userId: string) => {
|
|
25
29
|
renderCount.inc()
|
|
26
30
|
|
|
27
|
-
const [state, setState] = LiveStoreReact.useRow(
|
|
31
|
+
const [state, setState] = LiveStoreReact.useRow(tables.userInfo, userId)
|
|
28
32
|
return { state, setState }
|
|
29
33
|
},
|
|
30
34
|
{ wrapper, initialProps: 'u1' },
|
|
@@ -33,37 +37,29 @@ describe('useRow', () => {
|
|
|
33
37
|
expect(result.current.state.id).toBe('u1')
|
|
34
38
|
expect(result.current.state.username).toBe('')
|
|
35
39
|
expect(renderCount.val).toBe(1)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
store.mutate(
|
|
39
|
-
LiveStore.rawSqlMutation({
|
|
40
|
-
sql: LiveStore.sql`INSERT INTO UserInfo (id, username) VALUES ('u2', 'username_u2')`,
|
|
41
|
-
}),
|
|
42
|
-
),
|
|
43
|
-
)
|
|
40
|
+
expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
|
|
41
|
+
store.mutate(tables.userInfo.insert({ id: 'u2', username: 'username_u2' }))
|
|
44
42
|
|
|
45
43
|
rerender('u2')
|
|
46
44
|
|
|
45
|
+
expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
|
|
47
46
|
expect(result.current.state.id).toBe('u2')
|
|
48
47
|
expect(result.current.state.username).toBe('username_u2')
|
|
49
48
|
expect(renderCount.val).toBe(2)
|
|
50
|
-
})
|
|
49
|
+
}),
|
|
50
|
+
)
|
|
51
51
|
|
|
52
52
|
// TODO add a test that makes sure React doesn't re-render when a setter is used to set the same value
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
Vitest.scopedLive('should update the data reactively - via setState', () =>
|
|
55
55
|
Effect.gen(function* () {
|
|
56
|
-
const { wrapper,
|
|
57
|
-
useGlobalReactivityGraph: false,
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
const renderCount = makeRenderCount()
|
|
56
|
+
const { wrapper, renderCount } = yield* makeTodoMvcReact({})
|
|
61
57
|
|
|
62
|
-
const { result } = renderHook(
|
|
58
|
+
const { result } = ReactTesting.renderHook(
|
|
63
59
|
(userId: string) => {
|
|
64
60
|
renderCount.inc()
|
|
65
61
|
|
|
66
|
-
const [state, setState] = LiveStoreReact.useRow(
|
|
62
|
+
const [state, setState] = LiveStoreReact.useRow(tables.userInfo, userId)
|
|
67
63
|
return { state, setState }
|
|
68
64
|
},
|
|
69
65
|
{ wrapper, initialProps: 'u1' },
|
|
@@ -73,26 +69,23 @@ describe('useRow', () => {
|
|
|
73
69
|
expect(result.current.state.username).toBe('')
|
|
74
70
|
expect(renderCount.val).toBe(1)
|
|
75
71
|
|
|
76
|
-
|
|
72
|
+
ReactTesting.act(() => result.current.setState.username('username_u1_hello'))
|
|
77
73
|
|
|
78
74
|
expect(result.current.state.id).toBe('u1')
|
|
79
75
|
expect(result.current.state.username).toBe('username_u1_hello')
|
|
80
76
|
expect(renderCount.val).toBe(2)
|
|
81
|
-
})
|
|
77
|
+
}),
|
|
78
|
+
)
|
|
82
79
|
|
|
83
|
-
|
|
80
|
+
Vitest.scopedLive('should update the data reactively - via raw store mutation', () =>
|
|
84
81
|
Effect.gen(function* () {
|
|
85
|
-
const { wrapper, store,
|
|
86
|
-
useGlobalReactivityGraph: false,
|
|
87
|
-
})
|
|
82
|
+
const { wrapper, store, renderCount } = yield* makeTodoMvcReact({})
|
|
88
83
|
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
const { result } = renderHook(
|
|
84
|
+
const { result } = ReactTesting.renderHook(
|
|
92
85
|
(userId: string) => {
|
|
93
86
|
renderCount.inc()
|
|
94
87
|
|
|
95
|
-
const [state, setState] = LiveStoreReact.useRow(
|
|
88
|
+
const [state, setState] = LiveStoreReact.useRow(tables.userInfo, userId)
|
|
96
89
|
return { state, setState }
|
|
97
90
|
},
|
|
98
91
|
{ wrapper, initialProps: 'u1' },
|
|
@@ -102,36 +95,30 @@ describe('useRow', () => {
|
|
|
102
95
|
expect(result.current.state.username).toBe('')
|
|
103
96
|
expect(renderCount.val).toBe(1)
|
|
104
97
|
|
|
105
|
-
|
|
106
|
-
store.mutate(
|
|
107
|
-
LiveStore.rawSqlMutation({
|
|
108
|
-
sql: LiveStore.sql`UPDATE UserInfo SET username = 'username_u1_hello' WHERE id = 'u1';`,
|
|
109
|
-
}),
|
|
110
|
-
),
|
|
98
|
+
ReactTesting.act(() =>
|
|
99
|
+
store.mutate(tables.userInfo.update({ where: { id: 'u1' }, values: { username: 'username_u1_hello' } })),
|
|
111
100
|
)
|
|
112
101
|
|
|
113
102
|
expect(result.current.state.id).toBe('u1')
|
|
114
103
|
expect(result.current.state.username).toBe('username_u1_hello')
|
|
115
104
|
expect(renderCount.val).toBe(2)
|
|
116
|
-
})
|
|
105
|
+
}),
|
|
106
|
+
)
|
|
117
107
|
|
|
118
|
-
|
|
108
|
+
Vitest.scopedLive('should work for a larger app', () =>
|
|
119
109
|
Effect.gen(function* () {
|
|
120
|
-
const { wrapper, store,
|
|
121
|
-
useGlobalReactivityGraph: false,
|
|
122
|
-
})
|
|
110
|
+
const { wrapper, store, renderCount } = yield* makeTodoMvcReact({})
|
|
123
111
|
|
|
124
112
|
const allTodos$ = LiveStore.queryDb(
|
|
125
113
|
{ query: `select * from todos`, schema: Schema.Array(tables.todos.schema) },
|
|
126
|
-
{ label: 'allTodos'
|
|
114
|
+
{ label: 'allTodos' },
|
|
127
115
|
)
|
|
128
116
|
|
|
129
|
-
const appRouterRenderCount = makeRenderCount()
|
|
130
117
|
let globalSetState: LiveStoreReact.StateSetters<typeof AppRouterSchema> | undefined
|
|
131
118
|
const AppRouter: React.FC = () => {
|
|
132
|
-
|
|
119
|
+
renderCount.inc()
|
|
133
120
|
|
|
134
|
-
const [state, setState] = LiveStoreReact.useRow(AppRouterSchema
|
|
121
|
+
const [state, setState] = LiveStoreReact.useRow(AppRouterSchema)
|
|
135
122
|
|
|
136
123
|
globalSetState = setState
|
|
137
124
|
|
|
@@ -159,15 +146,15 @@ describe('useRow', () => {
|
|
|
159
146
|
}
|
|
160
147
|
|
|
161
148
|
const TaskDetails: React.FC<{ id: string }> = ({ id }) => {
|
|
162
|
-
const [todo] = LiveStoreReact.useRow(todos, id
|
|
149
|
+
const [todo] = LiveStoreReact.useRow(todos, id)
|
|
163
150
|
return <div role="content">{JSON.stringify(todo)}</div>
|
|
164
151
|
}
|
|
165
152
|
|
|
166
|
-
const renderResult = render(<AppRouter />, { wrapper })
|
|
153
|
+
const renderResult = ReactTesting.render(<AppRouter />, { wrapper })
|
|
167
154
|
|
|
168
|
-
expect(
|
|
155
|
+
expect(renderCount.val).toBe(1)
|
|
169
156
|
|
|
170
|
-
|
|
157
|
+
ReactTesting.act(() =>
|
|
171
158
|
store.mutate(
|
|
172
159
|
LiveStore.rawSqlMutation({
|
|
173
160
|
sql: LiveStore.sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)`,
|
|
@@ -175,19 +162,19 @@ describe('useRow', () => {
|
|
|
175
162
|
),
|
|
176
163
|
)
|
|
177
164
|
|
|
178
|
-
expect(
|
|
165
|
+
expect(renderCount.val).toBe(1)
|
|
179
166
|
expect(renderResult.getByRole('current-id').innerHTML).toMatchInlineSnapshot('"Current Task Id: -"')
|
|
180
167
|
|
|
181
|
-
|
|
168
|
+
ReactTesting.act(() => globalSetState!.currentTaskId('t1'))
|
|
182
169
|
|
|
183
|
-
expect(
|
|
170
|
+
expect(renderCount.val).toBe(2)
|
|
184
171
|
expect(renderResult.getByRole('content').innerHTML).toMatchInlineSnapshot(
|
|
185
172
|
`"{"id":"t1","text":"buy milk","completed":false}"`,
|
|
186
173
|
)
|
|
187
174
|
|
|
188
175
|
expect(renderResult.getByRole('current-id').innerHTML).toMatchInlineSnapshot('"Current Task Id: t1"')
|
|
189
176
|
|
|
190
|
-
|
|
177
|
+
ReactTesting.act(() =>
|
|
191
178
|
store.mutate(
|
|
192
179
|
LiveStore.rawSqlMutation({
|
|
193
180
|
sql: LiveStore.sql`INSERT INTO todos (id, text, completed) VALUES ('t2', 'buy eggs', 0)`,
|
|
@@ -199,37 +186,33 @@ describe('useRow', () => {
|
|
|
199
186
|
),
|
|
200
187
|
)
|
|
201
188
|
|
|
202
|
-
expect(
|
|
189
|
+
expect(renderCount.val).toBe(3)
|
|
203
190
|
expect(renderResult.getByRole('current-id').innerHTML).toMatchInlineSnapshot('"Current Task Id: t2"')
|
|
204
|
-
})
|
|
191
|
+
}),
|
|
192
|
+
)
|
|
205
193
|
|
|
206
|
-
|
|
194
|
+
Vitest.scopedLive('should work for a useRow query chained with a useTemporary query', () =>
|
|
207
195
|
Effect.gen(function* () {
|
|
208
|
-
const { store, wrapper,
|
|
209
|
-
useGlobalReactivityGraph: false,
|
|
210
|
-
})
|
|
211
|
-
const renderCount = makeRenderCount()
|
|
196
|
+
const { store, wrapper, renderCount } = yield* makeTodoMvcReact({})
|
|
212
197
|
|
|
213
198
|
store.mutate(
|
|
214
199
|
todos.insert({ id: 't1', text: 'buy milk', completed: false }),
|
|
215
200
|
todos.insert({ id: 't2', text: 'buy bread', completed: false }),
|
|
216
201
|
)
|
|
217
202
|
|
|
218
|
-
const { result, unmount, rerender } = renderHook(
|
|
203
|
+
const { result, unmount, rerender } = ReactTesting.renderHook(
|
|
219
204
|
(userId: string) => {
|
|
220
205
|
renderCount.inc()
|
|
221
206
|
|
|
222
|
-
const [_row, _setRow, rowState$] = LiveStoreReact.useRow(
|
|
223
|
-
const todos = LiveStoreReact.
|
|
224
|
-
(
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
),
|
|
232
|
-
userId,
|
|
207
|
+
const [_row, _setRow, rowState$] = LiveStoreReact.useRow(tables.userInfo, userId)
|
|
208
|
+
const todos = LiveStoreReact.useQuery(
|
|
209
|
+
LiveStore.queryDb(
|
|
210
|
+
(get) => tables.todos.query.where('text', 'LIKE', `%${get(rowState$).text}%`),
|
|
211
|
+
// TODO find a way where explicit `userId` is not needed here
|
|
212
|
+
// possibly by automatically understanding the `get(rowState$)` dependency
|
|
213
|
+
{ label: 'todosFiltered', deps: userId },
|
|
214
|
+
),
|
|
215
|
+
// TODO introduce a `deps` array which is only needed when a query is parametric
|
|
233
216
|
)
|
|
234
217
|
|
|
235
218
|
return { todos }
|
|
@@ -237,16 +220,9 @@ describe('useRow', () => {
|
|
|
237
220
|
{ wrapper, initialProps: 'u1' },
|
|
238
221
|
)
|
|
239
222
|
|
|
240
|
-
|
|
241
|
-
store.mutate(
|
|
242
|
-
LiveStore.rawSqlMutation({
|
|
243
|
-
sql: LiveStore.sql`INSERT INTO UserInfo (id, username, text) VALUES ('u2', 'username_u2', 'milk')`,
|
|
244
|
-
}),
|
|
245
|
-
),
|
|
246
|
-
)
|
|
223
|
+
ReactTesting.act(() => store.mutate(tables.userInfo.insert({ id: 'u2', username: 'username_u2', text: 'milk' })))
|
|
247
224
|
|
|
248
225
|
expect(result.current.todos.length).toBe(2)
|
|
249
|
-
// expect(result.current.state.username).toBe('')
|
|
250
226
|
expect(renderCount.val).toBe(1)
|
|
251
227
|
|
|
252
228
|
rerender('u2')
|
|
@@ -255,90 +231,80 @@ describe('useRow', () => {
|
|
|
255
231
|
expect(renderCount.val).toBe(2)
|
|
256
232
|
|
|
257
233
|
unmount()
|
|
258
|
-
})
|
|
259
|
-
|
|
260
|
-
let cachedProvider: BasicTracerProvider | undefined
|
|
234
|
+
}),
|
|
235
|
+
)
|
|
261
236
|
|
|
262
|
-
describe('otel', () => {
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
const provider = cachedProvider ?? new BasicTracerProvider()
|
|
266
|
-
cachedProvider = provider
|
|
267
|
-
provider.addSpanProcessor(new SimpleSpanProcessor(exporter))
|
|
237
|
+
Vitest.describe('otel', () => {
|
|
238
|
+
const provider = new BasicTracerProvider({})
|
|
268
239
|
provider.register()
|
|
269
240
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
241
|
+
it.each([{ strictMode: true }, { strictMode: false }])(
|
|
242
|
+
'should update the data based on component key strictMode=%s',
|
|
243
|
+
async ({ strictMode }) => {
|
|
244
|
+
const exporter = new InMemorySpanExporter()
|
|
274
245
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
const { wrapper, store, reactivityGraph, makeRenderCount, strictMode } = yield* makeTodoMvcReact({
|
|
278
|
-
useGlobalReactivityGraph: false,
|
|
279
|
-
otelContext,
|
|
280
|
-
otelTracer,
|
|
281
|
-
})
|
|
246
|
+
// const provider = cachedProvider ?? new BasicTracerProvider({ spanProcessors: [new SimpleSpanProcessor(exporter)] })
|
|
247
|
+
provider.addSpanProcessor(new SimpleSpanProcessor(exporter))
|
|
282
248
|
|
|
283
|
-
const
|
|
249
|
+
const otelTracer = otel.trace.getTracer(`testing-${strictMode ? 'strict' : 'non-strict'}`)
|
|
284
250
|
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
renderCount.inc()
|
|
288
|
-
|
|
289
|
-
const [state, setState] = LiveStoreReact.useRow(AppComponentSchema, userId, { reactivityGraph })
|
|
290
|
-
return { state, setState }
|
|
291
|
-
},
|
|
292
|
-
{ wrapper, initialProps: 'u1' },
|
|
293
|
-
)
|
|
251
|
+
const span = otelTracer.startSpan('test-root')
|
|
252
|
+
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
294
253
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
254
|
+
await Effect.gen(function* () {
|
|
255
|
+
const { wrapper, store, renderCount } = yield* makeTodoMvcReact({
|
|
256
|
+
otelContext,
|
|
257
|
+
otelTracer,
|
|
258
|
+
strictMode,
|
|
259
|
+
})
|
|
298
260
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
sql: LiveStore.sql`INSERT INTO UserInfo (id, username) VALUES ('u2', 'username_u2')`,
|
|
303
|
-
}),
|
|
304
|
-
),
|
|
305
|
-
)
|
|
261
|
+
const { result, rerender, unmount } = ReactTesting.renderHook(
|
|
262
|
+
(userId: string) => {
|
|
263
|
+
renderCount.inc()
|
|
306
264
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
unmount()
|
|
314
|
-
span.end()
|
|
315
|
-
|
|
316
|
-
return { strictMode }
|
|
317
|
-
}).pipe(Effect.scoped, Effect.tapCauseLogPretty, Effect.runPromise)
|
|
318
|
-
|
|
319
|
-
const mapAttributes = (attributes: otel.Attributes) => {
|
|
320
|
-
return ReadonlyRecord.map(attributes, (val, key) => {
|
|
321
|
-
if (key === 'stackInfo') {
|
|
322
|
-
const stackInfo = JSON.parse(val as string) as LiveStore.StackInfo
|
|
323
|
-
// stackInfo.frames.shift() // Removes `renderHook.wrapper` from the stack
|
|
324
|
-
stackInfo.frames.forEach((_) => {
|
|
325
|
-
if (_.name.includes('renderHook.wrapper')) {
|
|
326
|
-
_.name = 'renderHook.wrapper'
|
|
327
|
-
}
|
|
328
|
-
_.filePath = '__REPLACED_FOR_SNAPSHOT__'
|
|
329
|
-
})
|
|
330
|
-
return JSON.stringify(stackInfo)
|
|
331
|
-
}
|
|
332
|
-
return val
|
|
333
|
-
})
|
|
334
|
-
}
|
|
265
|
+
const [state, setState] = LiveStoreReact.useRow(tables.userInfo, userId)
|
|
266
|
+
return { state, setState }
|
|
267
|
+
},
|
|
268
|
+
{ wrapper, initialProps: 'u1' },
|
|
269
|
+
)
|
|
335
270
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
271
|
+
expect(result.current.state.id).toBe('u1')
|
|
272
|
+
expect(result.current.state.username).toBe('')
|
|
273
|
+
expect(renderCount.val).toBe(1)
|
|
274
|
+
|
|
275
|
+
// For u2 we'll make sure that the row already exists,
|
|
276
|
+
// so the lazy `insert` will be skipped
|
|
277
|
+
ReactTesting.act(() => store.mutate(tables.userInfo.insert({ id: 'u2', username: 'username_u2' })))
|
|
278
|
+
|
|
279
|
+
rerender('u2')
|
|
280
|
+
|
|
281
|
+
expect(result.current.state.id).toBe('u2')
|
|
282
|
+
expect(result.current.state.username).toBe('username_u2')
|
|
283
|
+
expect(renderCount.val).toBe(2)
|
|
284
|
+
|
|
285
|
+
unmount()
|
|
286
|
+
span.end()
|
|
287
|
+
}).pipe(Effect.scoped, Effect.tapCauseLogPretty, Effect.runPromise)
|
|
288
|
+
|
|
289
|
+
const mapAttributes = (attributes: otel.Attributes) => {
|
|
290
|
+
return ReadonlyRecord.map(attributes, (val, key) => {
|
|
291
|
+
if (key === 'firstStackInfo') {
|
|
292
|
+
const stackInfo = JSON.parse(val as string) as LiveStore.StackInfo
|
|
293
|
+
// stackInfo.frames.shift() // Removes `renderHook.wrapper` from the stack
|
|
294
|
+
stackInfo.frames.forEach((_) => {
|
|
295
|
+
if (_.name.includes('renderHook.wrapper')) {
|
|
296
|
+
_.name = 'renderHook.wrapper'
|
|
297
|
+
}
|
|
298
|
+
_.filePath = '__REPLACED_FOR_SNAPSHOT__'
|
|
299
|
+
})
|
|
300
|
+
return JSON.stringify(stackInfo)
|
|
301
|
+
}
|
|
302
|
+
return val
|
|
303
|
+
})
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
expect(getSimplifiedRootSpan(exporter, mapAttributes)).toMatchSnapshot()
|
|
307
|
+
},
|
|
308
|
+
)
|
|
343
309
|
})
|
|
344
310
|
})
|
package/src/useRow.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { QueryInfo, RowQuery } from '@livestore/common'
|
|
|
2
2
|
import { SessionIdSymbol } from '@livestore/common'
|
|
3
3
|
import { DbSchema } from '@livestore/common/schema'
|
|
4
4
|
import type { SqliteDsl } from '@livestore/db-schema'
|
|
5
|
-
import type { LiveQuery,
|
|
5
|
+
import type { LiveQuery, LiveQueryDef, Store } from '@livestore/livestore'
|
|
6
6
|
import { queryDb } from '@livestore/livestore'
|
|
7
7
|
import { shouldNeverHappen } from '@livestore/utils'
|
|
8
8
|
import { ReadonlyRecord } from '@livestore/utils/effect'
|
|
@@ -10,7 +10,6 @@ import React from 'react'
|
|
|
10
10
|
|
|
11
11
|
import { useStore } from './LiveStoreContext.js'
|
|
12
12
|
import { useQueryRef } from './useQuery.js'
|
|
13
|
-
import { useMakeScopedQuery } from './useScopedQuery.js'
|
|
14
13
|
|
|
15
14
|
export type UseRowResult<TTableDef extends DbSchema.TableDefBase> = [
|
|
16
15
|
row: RowQuery.Result<TTableDef>,
|
|
@@ -18,10 +17,6 @@ export type UseRowResult<TTableDef extends DbSchema.TableDefBase> = [
|
|
|
18
17
|
query$: LiveQuery<RowQuery.Result<TTableDef>, QueryInfo>,
|
|
19
18
|
]
|
|
20
19
|
|
|
21
|
-
export type UseRowOptionsBase = {
|
|
22
|
-
reactivityGraph?: ReactivityGraph
|
|
23
|
-
}
|
|
24
|
-
|
|
25
20
|
/**
|
|
26
21
|
* Similar to `React.useState` but returns a tuple of `[row, setRow, query$]` for a given table where ...
|
|
27
22
|
*
|
|
@@ -32,6 +27,7 @@ export type UseRowOptionsBase = {
|
|
|
32
27
|
* If the table is a singleton table, `useRow` can be called without an `id` argument. Otherwise, the `id` argument is required.
|
|
33
28
|
*/
|
|
34
29
|
export const useRow: {
|
|
30
|
+
// isSingleton: true
|
|
35
31
|
<
|
|
36
32
|
TTableDef extends DbSchema.TableDef<
|
|
37
33
|
DbSchema.DefaultSqliteTableDef,
|
|
@@ -39,8 +35,10 @@ export const useRow: {
|
|
|
39
35
|
>,
|
|
40
36
|
>(
|
|
41
37
|
table: TTableDef,
|
|
42
|
-
options?:
|
|
38
|
+
options?: { store?: Store },
|
|
43
39
|
): UseRowResult<TTableDef>
|
|
40
|
+
|
|
41
|
+
// isSingleton: false with requiredInsertColumnNames: 'id'
|
|
44
42
|
<
|
|
45
43
|
TTableDef extends DbSchema.TableDef<
|
|
46
44
|
DbSchema.DefaultSqliteTableDef,
|
|
@@ -54,8 +52,10 @@ export const useRow: {
|
|
|
54
52
|
table: TTableDef,
|
|
55
53
|
// TODO adjust so it works with arbitrary primary keys or unique constraints
|
|
56
54
|
id: string | SessionIdSymbol | number,
|
|
57
|
-
options?:
|
|
55
|
+
options?: Partial<RowQuery.RequiredColumnsOptions<TTableDef>> & { store?: Store },
|
|
58
56
|
): UseRowResult<TTableDef>
|
|
57
|
+
|
|
58
|
+
// isSingleton: false
|
|
59
59
|
<
|
|
60
60
|
TTableDef extends DbSchema.TableDef<
|
|
61
61
|
DbSchema.DefaultSqliteTableDef,
|
|
@@ -65,7 +65,7 @@ export const useRow: {
|
|
|
65
65
|
table: TTableDef,
|
|
66
66
|
// TODO adjust so it works with arbitrary primary keys or unique constraints
|
|
67
67
|
id: string | SessionIdSymbol | number,
|
|
68
|
-
options:
|
|
68
|
+
options: RowQuery.RequiredColumnsOptions<TTableDef> & { store?: Store },
|
|
69
69
|
): UseRowResult<TTableDef>
|
|
70
70
|
} = <
|
|
71
71
|
TTableDef extends DbSchema.TableDef<
|
|
@@ -74,19 +74,19 @@ export const useRow: {
|
|
|
74
74
|
>,
|
|
75
75
|
>(
|
|
76
76
|
table: TTableDef,
|
|
77
|
-
idOrOptions?: string | SessionIdSymbol | number |
|
|
78
|
-
options_?:
|
|
77
|
+
idOrOptions?: string | SessionIdSymbol | number | { store?: Store },
|
|
78
|
+
options_?: Partial<RowQuery.RequiredColumnsOptions<TTableDef>> & { store?: Store },
|
|
79
79
|
): UseRowResult<TTableDef> => {
|
|
80
80
|
const sqliteTableDef = table.sqliteDef
|
|
81
81
|
const id =
|
|
82
82
|
typeof idOrOptions === 'string' || idOrOptions === SessionIdSymbol || typeof idOrOptions === 'number'
|
|
83
83
|
? idOrOptions
|
|
84
84
|
: undefined
|
|
85
|
-
const options: (
|
|
85
|
+
const options: (Partial<RowQuery.RequiredColumnsOptions<TTableDef>> & { store?: Store }) | undefined =
|
|
86
86
|
typeof idOrOptions === 'string' || idOrOptions === SessionIdSymbol || typeof idOrOptions === 'number'
|
|
87
87
|
? options_
|
|
88
88
|
: idOrOptions
|
|
89
|
-
const { insertValues
|
|
89
|
+
const { insertValues } = options ?? {}
|
|
90
90
|
|
|
91
91
|
type TComponentState = SqliteDsl.FromColumns.RowDecoded<TTableDef['sqliteDef']['columns']>
|
|
92
92
|
|
|
@@ -96,7 +96,7 @@ export const useRow: {
|
|
|
96
96
|
shouldNeverHappen(`useRow called on table "${tableName}" which does not have 'deriveMutations: true' set`)
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
const { store } = useStore()
|
|
99
|
+
const { store } = useStore({ store: options?.store })
|
|
100
100
|
|
|
101
101
|
if (
|
|
102
102
|
store.schema.tables.has(table.sqliteDef.name) === false &&
|
|
@@ -110,28 +110,25 @@ export const useRow: {
|
|
|
110
110
|
const idVal = id === SessionIdSymbol ? 'session' : id
|
|
111
111
|
const rowQuery = table.query.row as any
|
|
112
112
|
|
|
113
|
-
type
|
|
114
|
-
const
|
|
115
|
-
(
|
|
113
|
+
type QueryDef = LiveQueryDef<RowQuery.Result<TTableDef>, QueryInfo.Row>
|
|
114
|
+
const queryDef: QueryDef = React.useMemo(
|
|
115
|
+
() =>
|
|
116
116
|
DbSchema.tableIsSingleton(table)
|
|
117
|
-
?
|
|
118
|
-
:
|
|
119
|
-
[idVal
|
|
120
|
-
{
|
|
121
|
-
otel: {
|
|
122
|
-
spanName: `LiveStore:useRow:${tableName}${idVal === undefined ? '' : `:${idVal}`}`,
|
|
123
|
-
attributes: { id: idVal },
|
|
124
|
-
},
|
|
125
|
-
},
|
|
117
|
+
? queryDb(rowQuery(), {})
|
|
118
|
+
: queryDb(rowQuery(id!, { insertValues: insertValues! }), { deps: idVal! }),
|
|
119
|
+
[id, insertValues, rowQuery, table, idVal],
|
|
126
120
|
)
|
|
127
121
|
|
|
128
|
-
const
|
|
122
|
+
const queryRef = useQueryRef(queryDef, {
|
|
123
|
+
otelSpanName: `LiveStore:useRow:${tableName}${idVal === undefined ? '' : `:${idVal}`}`,
|
|
124
|
+
store: options?.store,
|
|
125
|
+
})
|
|
129
126
|
|
|
130
127
|
const setState = React.useMemo<StateSetters<TTableDef>>(() => {
|
|
131
128
|
if (table.options.isSingleColumn) {
|
|
132
129
|
return (newValueOrFn: RowQuery.Result<TTableDef>) => {
|
|
133
|
-
const newValue = typeof newValueOrFn === 'function' ? newValueOrFn(
|
|
134
|
-
if (
|
|
130
|
+
const newValue = typeof newValueOrFn === 'function' ? newValueOrFn(queryRef.valueRef.current) : newValueOrFn
|
|
131
|
+
if (queryRef.valueRef.current === newValue) return
|
|
135
132
|
|
|
136
133
|
// NOTE we need to account for the short-hand syntax for single-column+singleton tables
|
|
137
134
|
if (table.options.isSingleton) {
|
|
@@ -146,11 +143,11 @@ export const useRow: {
|
|
|
146
143
|
ReadonlyRecord.map(sqliteTableDef.columns, (column, columnName) => (newValueOrFn: any) => {
|
|
147
144
|
const newValue =
|
|
148
145
|
// @ts-expect-error TODO fix typing
|
|
149
|
-
typeof newValueOrFn === 'function' ? newValueOrFn(
|
|
146
|
+
typeof newValueOrFn === 'function' ? newValueOrFn(queryRef.valueRef.current[columnName]) : newValueOrFn
|
|
150
147
|
|
|
151
148
|
// Don't update the state if it's the same as the value already seen in the component
|
|
152
149
|
// @ts-expect-error TODO fix typing
|
|
153
|
-
if (
|
|
150
|
+
if (queryRef.valueRef.current[columnName] === newValue) return
|
|
154
151
|
|
|
155
152
|
store.mutate(table.update({ where: { id: id ?? 'singleton' }, values: { [columnName]: newValue } }))
|
|
156
153
|
// store.mutate(updateMutationForQueryInfo(query$.queryInfo!, { [columnName]: newValue }))
|
|
@@ -159,13 +156,13 @@ export const useRow: {
|
|
|
159
156
|
setState.setMany = (columnValuesOrFn: Partial<TComponentState>) => {
|
|
160
157
|
const columnValues =
|
|
161
158
|
// @ts-expect-error TODO fix typing
|
|
162
|
-
typeof columnValuesOrFn === 'function' ? columnValuesOrFn(
|
|
159
|
+
typeof columnValuesOrFn === 'function' ? columnValuesOrFn(queryRef.valueRef.current) : columnValuesOrFn
|
|
163
160
|
|
|
164
161
|
// TODO use hashing instead
|
|
165
162
|
// Don't update the state if it's the same as the value already seen in the component
|
|
166
163
|
if (
|
|
167
164
|
// @ts-expect-error TODO fix typing
|
|
168
|
-
Object.entries(columnValues).every(([columnName, value]) =>
|
|
165
|
+
Object.entries(columnValues).every(([columnName, value]) => queryRef.valueRef.current[columnName] === value)
|
|
169
166
|
) {
|
|
170
167
|
return
|
|
171
168
|
}
|
|
@@ -176,9 +173,9 @@ export const useRow: {
|
|
|
176
173
|
|
|
177
174
|
return setState as any
|
|
178
175
|
}
|
|
179
|
-
}, [id,
|
|
176
|
+
}, [id, queryRef.valueRef, sqliteTableDef.columns, store, table])
|
|
180
177
|
|
|
181
|
-
return [
|
|
178
|
+
return [queryRef.valueRef.current, setState, queryRef.queryRcRef.value]
|
|
182
179
|
}
|
|
183
180
|
|
|
184
181
|
export type Dispatch<A> = (action: A) => void
|
|
@@ -12,7 +12,7 @@ import React from 'react'
|
|
|
12
12
|
*/
|
|
13
13
|
export const useStateRefWithReactiveInput = <T>(
|
|
14
14
|
inputState: T,
|
|
15
|
-
): [React.
|
|
15
|
+
): [React.RefObject<T>, (newState: T | ((prev: T) => T)) => void] => {
|
|
16
16
|
const [_, rerender] = React.useState(0)
|
|
17
17
|
|
|
18
18
|
const lastKnownInputStateRef = React.useRef<T>(inputState)
|