@livestore/react 0.2.0 → 0.3.0-dev.11
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/useRcRef.d.ts +72 -0
- package/dist/useRcRef.d.ts.map +1 -0
- package/dist/useRcRef.js +146 -0
- package/dist/useRcRef.js.map +1 -0
- package/dist/useRcRef.test.d.ts +2 -0
- package/dist/useRcRef.test.d.ts.map +1 -0
- package/dist/useRcRef.test.js +128 -0
- package/dist/useRcRef.test.js.map +1 -0
- 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/useScopedQuery.d.ts +10 -4
- package/dist/useScopedQuery.d.ts.map +1 -1
- package/dist/useScopedQuery.js +97 -52
- package/dist/useScopedQuery.js.map +1 -1
- package/dist/useScopedQuery.test.js +13 -12
- package/dist/useScopedQuery.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 -151
- 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/useTemporaryQuery.d.ts +0 -22
- package/dist/useTemporaryQuery.d.ts.map +0 -1
- package/dist/useTemporaryQuery.js +0 -75
- package/dist/useTemporaryQuery.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,
|
|
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,
|
|
68
|
-
options:
|
|
68
|
+
options: RowQuery.RequiredColumnsOptions<TTableDef> & { store?: Store },
|
|
69
69
|
): UseRowResult<TTableDef>
|
|
70
70
|
} = <
|
|
71
71
|
TTableDef extends DbSchema.TableDef<
|
|
@@ -74,14 +74,14 @@ export const useRow: {
|
|
|
74
74
|
>,
|
|
75
75
|
>(
|
|
76
76
|
table: TTableDef,
|
|
77
|
-
idOrOptions?: string | SessionIdSymbol |
|
|
78
|
-
options_?:
|
|
77
|
+
idOrOptions?: string | SessionIdSymbol | { store?: Store },
|
|
78
|
+
options_?: Partial<RowQuery.RequiredColumnsOptions<TTableDef>> & { store?: Store },
|
|
79
79
|
): UseRowResult<TTableDef> => {
|
|
80
80
|
const sqliteTableDef = table.sqliteDef
|
|
81
81
|
const id = typeof idOrOptions === 'string' || idOrOptions === SessionIdSymbol ? idOrOptions : undefined
|
|
82
|
-
const options: (
|
|
82
|
+
const options: (Partial<RowQuery.RequiredColumnsOptions<TTableDef>> & { store?: Store }) | undefined =
|
|
83
83
|
typeof idOrOptions === 'string' || idOrOptions === SessionIdSymbol ? options_ : idOrOptions
|
|
84
|
-
const { insertValues
|
|
84
|
+
const { insertValues } = options ?? {}
|
|
85
85
|
|
|
86
86
|
type TComponentState = SqliteDsl.FromColumns.RowDecoded<TTableDef['sqliteDef']['columns']>
|
|
87
87
|
|
|
@@ -91,7 +91,7 @@ export const useRow: {
|
|
|
91
91
|
shouldNeverHappen(`useRow called on table "${tableName}" which does not have 'deriveMutations: true' set`)
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
const { store } = useStore()
|
|
94
|
+
const { store } = useStore({ store: options?.store })
|
|
95
95
|
|
|
96
96
|
if (
|
|
97
97
|
store.schema.tables.has(table.sqliteDef.name) === false &&
|
|
@@ -105,28 +105,25 @@ export const useRow: {
|
|
|
105
105
|
const idStr = id === SessionIdSymbol ? 'session' : id
|
|
106
106
|
const rowQuery = table.query.row as any
|
|
107
107
|
|
|
108
|
-
type
|
|
109
|
-
const
|
|
110
|
-
(
|
|
108
|
+
type QueryDef = LiveQueryDef<RowQuery.Result<TTableDef>, QueryInfo.Row>
|
|
109
|
+
const queryDef: QueryDef = React.useMemo(
|
|
110
|
+
() =>
|
|
111
111
|
DbSchema.tableIsSingleton(table)
|
|
112
|
-
?
|
|
113
|
-
:
|
|
114
|
-
[idStr
|
|
115
|
-
{
|
|
116
|
-
otel: {
|
|
117
|
-
spanName: `LiveStore:useRow:${tableName}${idStr === undefined ? '' : `:${idStr}`}`,
|
|
118
|
-
attributes: { id: idStr },
|
|
119
|
-
},
|
|
120
|
-
},
|
|
112
|
+
? queryDb(rowQuery(), {})
|
|
113
|
+
: queryDb(rowQuery(id!, { insertValues: insertValues! }), { deps: idStr! }),
|
|
114
|
+
[id, insertValues, rowQuery, table, idStr],
|
|
121
115
|
)
|
|
122
116
|
|
|
123
|
-
const
|
|
117
|
+
const queryRef = useQueryRef(queryDef, {
|
|
118
|
+
otelSpanName: `LiveStore:useRow:${tableName}${idStr === undefined ? '' : `:${idStr}`}`,
|
|
119
|
+
store: options?.store,
|
|
120
|
+
})
|
|
124
121
|
|
|
125
122
|
const setState = React.useMemo<StateSetters<TTableDef>>(() => {
|
|
126
123
|
if (table.options.isSingleColumn) {
|
|
127
124
|
return (newValueOrFn: RowQuery.Result<TTableDef>) => {
|
|
128
|
-
const newValue = typeof newValueOrFn === 'function' ? newValueOrFn(
|
|
129
|
-
if (
|
|
125
|
+
const newValue = typeof newValueOrFn === 'function' ? newValueOrFn(queryRef.valueRef.current) : newValueOrFn
|
|
126
|
+
if (queryRef.valueRef.current === newValue) return
|
|
130
127
|
|
|
131
128
|
// NOTE we need to account for the short-hand syntax for single-column+singleton tables
|
|
132
129
|
if (table.options.isSingleton) {
|
|
@@ -141,11 +138,11 @@ export const useRow: {
|
|
|
141
138
|
ReadonlyRecord.map(sqliteTableDef.columns, (column, columnName) => (newValueOrFn: any) => {
|
|
142
139
|
const newValue =
|
|
143
140
|
// @ts-expect-error TODO fix typing
|
|
144
|
-
typeof newValueOrFn === 'function' ? newValueOrFn(
|
|
141
|
+
typeof newValueOrFn === 'function' ? newValueOrFn(queryRef.valueRef.current[columnName]) : newValueOrFn
|
|
145
142
|
|
|
146
143
|
// Don't update the state if it's the same as the value already seen in the component
|
|
147
144
|
// @ts-expect-error TODO fix typing
|
|
148
|
-
if (
|
|
145
|
+
if (queryRef.valueRef.current[columnName] === newValue) return
|
|
149
146
|
|
|
150
147
|
store.mutate(table.update({ where: { id: id ?? 'singleton' }, values: { [columnName]: newValue } }))
|
|
151
148
|
// store.mutate(updateMutationForQueryInfo(query$.queryInfo!, { [columnName]: newValue }))
|
|
@@ -154,13 +151,13 @@ export const useRow: {
|
|
|
154
151
|
setState.setMany = (columnValuesOrFn: Partial<TComponentState>) => {
|
|
155
152
|
const columnValues =
|
|
156
153
|
// @ts-expect-error TODO fix typing
|
|
157
|
-
typeof columnValuesOrFn === 'function' ? columnValuesOrFn(
|
|
154
|
+
typeof columnValuesOrFn === 'function' ? columnValuesOrFn(queryRef.valueRef.current) : columnValuesOrFn
|
|
158
155
|
|
|
159
156
|
// TODO use hashing instead
|
|
160
157
|
// Don't update the state if it's the same as the value already seen in the component
|
|
161
158
|
if (
|
|
162
159
|
// @ts-expect-error TODO fix typing
|
|
163
|
-
Object.entries(columnValues).every(([columnName, value]) =>
|
|
160
|
+
Object.entries(columnValues).every(([columnName, value]) => queryRef.valueRef.current[columnName] === value)
|
|
164
161
|
) {
|
|
165
162
|
return
|
|
166
163
|
}
|
|
@@ -171,9 +168,9 @@ export const useRow: {
|
|
|
171
168
|
|
|
172
169
|
return setState as any
|
|
173
170
|
}
|
|
174
|
-
}, [id,
|
|
171
|
+
}, [id, queryRef.valueRef, sqliteTableDef.columns, store, table])
|
|
175
172
|
|
|
176
|
-
return [
|
|
173
|
+
return [queryRef.valueRef.current, setState, queryRef.queryRcRef.value]
|
|
177
174
|
}
|
|
178
175
|
|
|
179
176
|
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)
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import type { QueryInfo } from '@livestore/common';
|
|
2
|
-
import type { LiveQuery } from '@livestore/livestore';
|
|
3
|
-
import * as otel from '@opentelemetry/api';
|
|
4
|
-
import React from 'react';
|
|
5
|
-
export type DepKey = string | number | ReadonlyArray<string | number>;
|
|
6
|
-
/**
|
|
7
|
-
* Creates a query, subscribes and destroys it when the component unmounts.
|
|
8
|
-
*
|
|
9
|
-
* The `key` is used to determine whether the a new query should be created or if the existing one should be reused.
|
|
10
|
-
*/
|
|
11
|
-
export declare const useScopedQuery: <TResult>(makeQuery: () => LiveQuery<TResult>, key: DepKey) => TResult;
|
|
12
|
-
export declare const useScopedQueryRef: <TResult>(makeQuery: () => LiveQuery<TResult>, key: DepKey) => React.MutableRefObject<TResult>;
|
|
13
|
-
export declare const useMakeScopedQuery: <TResult, TQueryInfo extends QueryInfo>(makeQuery: (otelContext: otel.Context) => LiveQuery<TResult, TQueryInfo>, key: DepKey, options?: {
|
|
14
|
-
otel?: {
|
|
15
|
-
spanName?: string;
|
|
16
|
-
attributes?: otel.Attributes;
|
|
17
|
-
};
|
|
18
|
-
}) => {
|
|
19
|
-
query$: LiveQuery<TResult, TQueryInfo>;
|
|
20
|
-
otelContext: otel.Context;
|
|
21
|
-
};
|
|
22
|
-
//# sourceMappingURL=useTemporaryQuery.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useTemporaryQuery.d.ts","sourceRoot":"","sources":["../src/useTemporaryQuery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AACrD,OAAO,KAAK,IAAI,MAAM,oBAAoB,CAAA;AAC1C,OAAO,KAAK,MAAM,OAAO,CAAA;AAuBzB,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,aAAa,CAAC,MAAM,GAAG,MAAM,CAAC,CAAA;AAErE;;;;GAIG;AACH,eAAO,MAAM,cAAc,GAAI,OAAO,aAAa,MAAM,SAAS,CAAC,OAAO,CAAC,OAAO,MAAM,KAAG,OAChD,CAAA;AAE3C,eAAO,MAAM,iBAAiB,GAAI,OAAO,aAC5B,MAAM,SAAS,CAAC,OAAO,CAAC,OAC9B,MAAM,KACV,KAAK,CAAC,gBAAgB,CAAC,OAAO,CAIhC,CAAA;AAED,eAAO,MAAM,kBAAkB,GAAI,OAAO,EAAE,UAAU,SAAS,SAAS,aAC3D,CAAC,WAAW,EAAE,IAAI,CAAC,OAAO,KAAK,SAAS,CAAC,OAAO,EAAE,UAAU,CAAC,OACnE,MAAM,YACD;IACR,IAAI,CAAC,EAAE;QACL,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,UAAU,CAAC,EAAE,IAAI,CAAC,UAAU,CAAA;KAC7B,CAAA;CACF,KACA;IAAE,MAAM,EAAE,SAAS,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IAAC,WAAW,EAAE,IAAI,CAAC,OAAO,CAAA;CA4ErE,CAAA"}
|