@livequery/react 2.0.133 → 2.0.135
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 +144 -90
- package/dist/createContextFromHook.d.ts.map +1 -1
- package/dist/createContextFromHook.js +5 -1
- package/dist/createContextFromHook.js.map +1 -1
- package/dist/useAction.d.ts.map +1 -1
- package/dist/useAction.js +9 -3
- package/dist/useAction.js.map +1 -1
- package/dist/useObservable.d.ts +2 -1
- package/dist/useObservable.d.ts.map +1 -1
- package/dist/useObservable.js +21 -15
- package/dist/useObservable.js.map +1 -1
- package/package.json +66 -61
package/README.md
CHANGED
|
@@ -2,18 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Thin React bindings for `@livequery/client`.
|
|
4
4
|
|
|
5
|
-
This
|
|
6
|
-
|
|
7
|
-
This package provides a small set of hooks and helpers for wiring a `LivequeryClient` instance into a React app, subscribing to RxJS streams, and reading collection or document state from `@livequery/client`.
|
|
8
|
-
|
|
9
|
-
## AI Agent Guidance
|
|
10
|
-
|
|
11
|
-
Repository-specific agent guidance lives in `AGENTS.md` and `copilot-instructions.md`.
|
|
12
|
-
|
|
13
|
-
- `AGENTS.md` is the implementation-focused guide for coding agents modifying this package.
|
|
14
|
-
- `copilot-instructions.md` provides repo-level instructions for Copilot when generating or reviewing code in this workspace.
|
|
15
|
-
- Both documents assume this repo is a React bindings library package, so agent changes should avoid app-specific scaffolding and should preserve public API compatibility by default.
|
|
16
|
-
- Agents generating consumer code should create one shared `LivequeryClient`, provide it through `LivequeryClientProvider`, and subscribe to collection state with `useObservable()`.
|
|
5
|
+
This package is a small integration layer. It does not implement transport, storage, caching, or query behavior itself. Those responsibilities stay in `@livequery/client`; this package gives React components a clean way to access a shared `LivequeryClient`, create `LivequeryCollection` instances, and mirror RxJS streams into render state.
|
|
17
6
|
|
|
18
7
|
## Install
|
|
19
8
|
|
|
@@ -34,12 +23,12 @@ npm install @livequery/react @livequery/client react rxjs
|
|
|
34
23
|
- `useCollection`
|
|
35
24
|
- `useDocument`
|
|
36
25
|
- `useObservable`
|
|
37
|
-
- `
|
|
26
|
+
- `useAction`
|
|
38
27
|
- `createContextFromHook`
|
|
39
28
|
|
|
40
|
-
##
|
|
29
|
+
## Recommended App Shape
|
|
41
30
|
|
|
42
|
-
|
|
31
|
+
Create one `LivequeryClient` for your app or data boundary, provide it once, then use hooks inside descendant components.
|
|
43
32
|
|
|
44
33
|
```tsx
|
|
45
34
|
import { LivequeryClient } from '@livequery/client'
|
|
@@ -51,18 +40,58 @@ const client = new LivequeryClient({
|
|
|
51
40
|
|
|
52
41
|
export function AppProviders({ children }: { children: React.ReactNode }) {
|
|
53
42
|
return (
|
|
54
|
-
<LivequeryClientProvider
|
|
43
|
+
<LivequeryClientProvider core={client}>
|
|
55
44
|
{children}
|
|
56
45
|
</LivequeryClientProvider>
|
|
57
46
|
)
|
|
58
47
|
}
|
|
59
48
|
```
|
|
60
49
|
|
|
61
|
-
|
|
50
|
+
Use collection methods from effects or event handlers, not during render. Use `useObservable()` to subscribe to reactive fields before rendering their values.
|
|
51
|
+
|
|
52
|
+
## `LivequeryClientProvider`
|
|
53
|
+
|
|
54
|
+
`LivequeryClientProvider` makes a shared `LivequeryClient` available to `useCollection()` and any component that calls `useLivequeryClient()`.
|
|
55
|
+
|
|
56
|
+
Use it when:
|
|
57
|
+
|
|
58
|
+
- an app has one shared Livequery connection
|
|
59
|
+
- a route, workspace, tenant, or feature boundary needs its own client
|
|
60
|
+
- hooks below the boundary should avoid receiving the client as a prop
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
<LivequeryClientProvider core={client}>
|
|
64
|
+
<TodoList />
|
|
65
|
+
</LivequeryClientProvider>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The provider currently expects a `core` prop. Passing `client` will not work unless the implementation is changed.
|
|
69
|
+
|
|
70
|
+
## `useLivequeryClient`
|
|
71
|
+
|
|
72
|
+
`useLivequeryClient()` reads the nearest `LivequeryClient` from `LivequeryClientProvider`.
|
|
73
|
+
|
|
74
|
+
Use it when you need direct access to the shared client, usually for setup code or integration with APIs that are not covered by `useCollection()`.
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
import { useLivequeryClient } from '@livequery/react'
|
|
78
|
+
|
|
79
|
+
export function ClientStatus() {
|
|
80
|
+
const client = useLivequeryClient()
|
|
81
|
+
return <span>{client ? 'Connected' : 'Missing client'}</span>
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The hook must be used under a matching provider. If it is called outside the provider tree, the generated context hook throws `Context provider is missing`.
|
|
62
86
|
|
|
63
|
-
`useCollection`
|
|
87
|
+
## `useCollection`
|
|
88
|
+
|
|
89
|
+
`useCollection<T>(ref, options)` creates one `LivequeryCollection<T>` for the component, initializes it when `ref` is truthy, and returns the collection instance.
|
|
90
|
+
|
|
91
|
+
Use it when a component needs the full collection API: reactive state plus methods such as querying or mutations.
|
|
64
92
|
|
|
65
93
|
```tsx
|
|
94
|
+
import { useEffect } from 'react'
|
|
66
95
|
import { useCollection, useObservable } from '@livequery/react'
|
|
67
96
|
|
|
68
97
|
type Todo = {
|
|
@@ -75,8 +104,14 @@ export function TodoList() {
|
|
|
75
104
|
const collection = useCollection<Todo>('todos', { lazy: false })
|
|
76
105
|
const items = useObservable(collection.items, [])
|
|
77
106
|
const loading = useObservable(collection.loading, false)
|
|
107
|
+
const error = useObservable(collection.error)
|
|
108
|
+
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
collection.query()
|
|
111
|
+
}, [collection])
|
|
78
112
|
|
|
79
113
|
if (loading) return <p>Loading...</p>
|
|
114
|
+
if (error) return <p>Could not load todos.</p>
|
|
80
115
|
|
|
81
116
|
return (
|
|
82
117
|
<ul>
|
|
@@ -88,15 +123,21 @@ export function TodoList() {
|
|
|
88
123
|
}
|
|
89
124
|
```
|
|
90
125
|
|
|
91
|
-
|
|
126
|
+
Behavior notes:
|
|
92
127
|
|
|
93
|
-
- `ref`
|
|
128
|
+
- `ref` may be `undefined`, `null`, `false`, or an empty string. Falsy refs skip initialization.
|
|
94
129
|
- The same hook call keeps one collection instance for the lifetime of the component.
|
|
95
|
-
-
|
|
130
|
+
- `options` are used when that collection instance is first created. Pass stable options, or remount the hook if options need to change.
|
|
131
|
+
- Subscribe to fields such as `collection.items`, `collection.loading`, and `collection.error` with `useObservable()`.
|
|
132
|
+
- Do not call `query()`, `add()`, `update()`, or `delete()` directly during render.
|
|
133
|
+
|
|
134
|
+
## `useDocument`
|
|
96
135
|
|
|
97
|
-
|
|
136
|
+
`useDocument<T>(ref, options)` is a document-focused convenience wrapper over `useCollection()`.
|
|
98
137
|
|
|
99
|
-
|
|
138
|
+
It initializes a collection for a document ref, subscribes to collection items and loading state, then returns `[items[0], loading]`.
|
|
139
|
+
|
|
140
|
+
Use it when a component only needs one document and a loading flag.
|
|
100
141
|
|
|
101
142
|
```tsx
|
|
102
143
|
import { useDocument } from '@livequery/react'
|
|
@@ -117,9 +158,13 @@ export function TodoDetail({ id }: { id: string }) {
|
|
|
117
158
|
}
|
|
118
159
|
```
|
|
119
160
|
|
|
120
|
-
|
|
161
|
+
Use `useCollection()` instead when you need collection methods, error state, multiple documents, or more control over subscriptions.
|
|
162
|
+
|
|
163
|
+
## `useObservable`
|
|
164
|
+
|
|
165
|
+
`useObservable()` bridges an RxJS `Observable` or `BehaviorSubject` into React state.
|
|
121
166
|
|
|
122
|
-
|
|
167
|
+
Use it for any reactive value that should cause a component rerender when it emits.
|
|
123
168
|
|
|
124
169
|
```tsx
|
|
125
170
|
import { BehaviorSubject } from 'rxjs'
|
|
@@ -133,65 +178,58 @@ export function Counter() {
|
|
|
133
178
|
}
|
|
134
179
|
```
|
|
135
180
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
## useGlobalValue
|
|
139
|
-
|
|
140
|
-
`useGlobalValue` stores a lazily created singleton on `globalThis`. This is useful when an app should reuse one object across renders or across multiple React roots in the same runtime.
|
|
181
|
+
Supported shapes:
|
|
141
182
|
|
|
142
183
|
```tsx
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
export function useAppClient() {
|
|
147
|
-
return useGlobalValue('livequery-client', () => {
|
|
148
|
-
return new LivequeryClient({
|
|
149
|
-
endpoint: 'https://your-livequery-server'
|
|
150
|
-
})
|
|
151
|
-
})
|
|
152
|
-
}
|
|
184
|
+
const value = useObservable(source$)
|
|
185
|
+
const valueWithDefault = useObservable(source$, defaultValue)
|
|
186
|
+
const lazyValue = useObservable(() => source$)
|
|
153
187
|
```
|
|
154
188
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
`createContextFromHook` is the most distinctive helper in this package. It lets you define shared state once at the provider boundary, but consume it later with an API that still feels like a normal hook.
|
|
189
|
+
Behavior notes:
|
|
158
190
|
|
|
159
|
-
|
|
191
|
+
- `BehaviorSubject` is treated specially. Its initial value is read with `getValue()` so the first render can use the current value.
|
|
192
|
+
- Lazy sources are resolved once for the hook lifetime.
|
|
193
|
+
- If the source is `undefined`, the hook returns the default value, or `undefined` if no default was provided.
|
|
194
|
+
- Reading `.value` or `.getValue()` manually in render is not a replacement for `useObservable()` because it will not subscribe the component to future emissions.
|
|
160
195
|
|
|
161
|
-
|
|
162
|
-
- "I want descendants to read the computed result through a hook"
|
|
196
|
+
## `useAction`
|
|
163
197
|
|
|
164
|
-
|
|
198
|
+
`useAction(fn, options)` wraps an async function and attaches action state to the returned callable.
|
|
165
199
|
|
|
166
|
-
|
|
200
|
+
Use it for button clicks, form submissions, and other event-driven async work where the UI needs `loading`, `data`, or `error`.
|
|
167
201
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
That means consumers do not need to know about `React.createContext`, context objects, or provider value wiring. They only call a hook.
|
|
172
|
-
|
|
173
|
-
### Why this helper is useful
|
|
174
|
-
|
|
175
|
-
Compared to creating context by hand, `createContextFromHook` removes the repetitive parts:
|
|
202
|
+
```tsx
|
|
203
|
+
import { useAction } from '@livequery/react'
|
|
176
204
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
205
|
+
export function SaveButton({ save }: { save: () => Promise<{ id: string }> }) {
|
|
206
|
+
const saveAction = useAction(save, {
|
|
207
|
+
onError(error) {
|
|
208
|
+
console.error(error)
|
|
209
|
+
}
|
|
210
|
+
})
|
|
180
211
|
|
|
181
|
-
|
|
212
|
+
return (
|
|
213
|
+
<button disabled={saveAction.loading} onClick={() => void saveAction()}>
|
|
214
|
+
{saveAction.loading ? 'Saving...' : 'Save'}
|
|
215
|
+
</button>
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
```
|
|
182
219
|
|
|
183
|
-
The
|
|
220
|
+
The returned function has these state fields:
|
|
184
221
|
|
|
185
|
-
|
|
222
|
+
- `loading`: `true` while the latest call is pending
|
|
223
|
+
- `data`: resolved data from the latest successful call
|
|
224
|
+
- `error`: normalized `{ code, message }` from the latest failed call
|
|
186
225
|
|
|
187
|
-
|
|
226
|
+
If multiple calls overlap, only the latest call is allowed to update state. Earlier calls still resolve or reject normally, but they will not overwrite the visible action state after a newer call has started.
|
|
188
227
|
|
|
189
|
-
|
|
190
|
-
2. A consumer-side hook: `useValue()` reads that computed value from the nearest provider.
|
|
228
|
+
## `createContextFromHook`
|
|
191
229
|
|
|
192
|
-
|
|
230
|
+
`createContextFromHook(fn)` derives a provider and a consumer hook from one factory function.
|
|
193
231
|
|
|
194
|
-
|
|
232
|
+
Use it when a value should be computed at a provider boundary and consumed through a hook.
|
|
195
233
|
|
|
196
234
|
```tsx
|
|
197
235
|
import { createContextFromHook } from '@livequery/react'
|
|
@@ -214,42 +252,58 @@ function App() {
|
|
|
214
252
|
}
|
|
215
253
|
```
|
|
216
254
|
|
|
217
|
-
|
|
255
|
+
The helper returns:
|
|
218
256
|
|
|
219
|
-
|
|
257
|
+
- `useValue`: reads the current context value
|
|
258
|
+
- `Provider`: receives props, calls `fn(props)`, and stores the returned value in context
|
|
220
259
|
|
|
221
|
-
|
|
260
|
+
Behavior notes:
|
|
222
261
|
|
|
223
|
-
|
|
262
|
+
- The provider calls `fn(props)` on every provider render.
|
|
263
|
+
- `createContextFromHook()` does not memoize the returned value. Memoize inside `fn` or stabilize provider props if recomputation matters.
|
|
264
|
+
- The generated hook throws `Context provider is missing` when consumed outside its provider.
|
|
224
265
|
|
|
225
|
-
|
|
266
|
+
`LivequeryClientProvider` and `useLivequeryClient` are built with this helper.
|
|
226
267
|
|
|
227
|
-
|
|
228
|
-
- deriving session or auth state from provider props
|
|
229
|
-
- wrapping feature-specific state that should be consumed through a single custom hook
|
|
230
|
-
- hiding context implementation details from package consumers
|
|
268
|
+
## Choosing The Right API
|
|
231
269
|
|
|
232
|
-
|
|
270
|
+
- Use `LivequeryClientProvider` once near the app or data boundary.
|
|
271
|
+
- Use `useLivequeryClient()` only when you need direct client access.
|
|
272
|
+
- Use `useCollection()` when you need collection methods or multiple reactive collection fields.
|
|
273
|
+
- Use `useDocument()` when you only need the first document and loading state.
|
|
274
|
+
- Use `useObservable()` whenever an RxJS source should drive rendering.
|
|
275
|
+
- Use `useAction()` for async event handlers that need loading, data, and error state.
|
|
276
|
+
- Use `createContextFromHook()` for package or app utilities that should expose provider plus hook pairs.
|
|
233
277
|
|
|
234
|
-
|
|
235
|
-
- The consumer hook assumes a provider exists. In the current implementation it casts the context value to `R`, so using the hook outside its provider can result in `undefined` flowing through at runtime.
|
|
236
|
-
- `createContextFromHook` does not add memoization by itself. If `fn(props)` is expensive, memoize inside `fn` or stabilize the incoming props.
|
|
278
|
+
## Common Mistakes
|
|
237
279
|
|
|
238
|
-
|
|
280
|
+
- Creating a new `LivequeryClient` inside a component render.
|
|
281
|
+
- Calling collection mutations directly during render.
|
|
282
|
+
- Reading `BehaviorSubject` values manually and expecting rerenders.
|
|
283
|
+
- Passing changing `useCollection()` options and expecting the existing collection instance to rebuild.
|
|
284
|
+
- Using `useDocument()` when you need error state or collection methods.
|
|
285
|
+
- Importing APIs not listed in the `Exports` section.
|
|
239
286
|
|
|
240
|
-
|
|
287
|
+
## Build
|
|
241
288
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
4. pass that value to the provider
|
|
289
|
+
```bash
|
|
290
|
+
bun run build
|
|
291
|
+
```
|
|
246
292
|
|
|
247
|
-
|
|
293
|
+
Build output is published from `dist/` and exposed through the package `exports` field.
|
|
248
294
|
|
|
249
|
-
##
|
|
295
|
+
## Test
|
|
250
296
|
|
|
251
297
|
```bash
|
|
252
|
-
bun
|
|
298
|
+
bun test
|
|
253
299
|
```
|
|
254
300
|
|
|
255
|
-
|
|
301
|
+
The current test suite covers the React hook behavior that is most sensitive to regressions: observable subscriptions, lazy observable sources, generated context hooks, and async action race handling.
|
|
302
|
+
|
|
303
|
+
## Agent Guidance
|
|
304
|
+
|
|
305
|
+
Repository-specific coding-agent guidance lives in `AGENTS.md`, `AGENT_API_GUIDE.md`, and `copilot-instructions.md`.
|
|
306
|
+
|
|
307
|
+
- `README.md` is end-user documentation.
|
|
308
|
+
- `AGENTS.md` is the implementation-focused entry point for coding agents.
|
|
309
|
+
- `AGENT_API_GUIDE.md` explains how agents should choose and use each public API when generating code or modifying this package.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createContextFromHook.d.ts","sourceRoot":"","sources":["../src/createContextFromHook.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA6B,KAAK,GAAG,EAAE,KAAK,iBAAiB,EAAE,MAAM,OAAO,CAAA;AAGnF,eAAO,MAAM,qBAAqB,GAAI,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"createContextFromHook.d.ts","sourceRoot":"","sources":["../src/createContextFromHook.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA6B,KAAK,GAAG,EAAE,KAAK,iBAAiB,EAAE,MAAM,OAAO,CAAA;AAGnF,eAAO,MAAM,qBAAqB,GAAI,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,KAkBzC,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,OAAO,CACzF,CAAA"}
|
|
@@ -3,7 +3,11 @@ import { createContext, useContext } from "react";
|
|
|
3
3
|
export const createContextFromHook = (fn) => {
|
|
4
4
|
const ctx = createContext(undefined);
|
|
5
5
|
const useState = () => {
|
|
6
|
-
|
|
6
|
+
const value = useContext(ctx);
|
|
7
|
+
if (value === undefined) {
|
|
8
|
+
throw new Error('Context provider is missing');
|
|
9
|
+
}
|
|
10
|
+
return value;
|
|
7
11
|
};
|
|
8
12
|
const Provider = ({ children, ...props }) => {
|
|
9
13
|
const value = fn(props);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createContextFromHook.js","sourceRoot":"","sources":["../src/createContextFromHook.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAoC,MAAM,OAAO,CAAA;AAGnF,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAO,EAAiC,EAAE,EAAE;IAC7E,MAAM,GAAG,GAAG,aAAa,CAAgB,SAAS,CAAC,CAAA;IAEnD,MAAM,QAAQ,GAAG,GAAG,EAAE;QAClB,
|
|
1
|
+
{"version":3,"file":"createContextFromHook.js","sourceRoot":"","sources":["../src/createContextFromHook.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAoC,MAAM,OAAO,CAAA;AAGnF,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAO,EAAiC,EAAE,EAAE;IAC7E,MAAM,GAAG,GAAG,aAAa,CAAgB,SAAS,CAAC,CAAA;IAEnD,MAAM,QAAQ,GAAG,GAAG,EAAE;QAClB,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,CAAA;QAC7B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAA;QAClD,CAAC;QACD,OAAO,KAAK,CAAA;IAChB,CAAC,CAAA;IACD,MAAM,QAAQ,GAAG,CAAC,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAwB,EAAE,EAAE;QAC9D,MAAM,KAAK,GAAG,EAAE,CAAC,KAAU,CAAC,CAAA;QAC5B,OAAO,CACH,KAAC,GAAG,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YACrB,QAAQ,GACE,CAClB,CAAA;IACL,CAAC,CAAA;IACD,OAAO,CAAC,QAAQ,EAAE,QAAQ,EAA6D,CAAA;AAC3F,CAAC,CAAA"}
|
package/dist/useAction.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAction.d.ts","sourceRoot":"","sources":["../src/useAction.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,SAAS,GAAI,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,EAAE,UAAS,OAAO,CAAC;IAAE,OAAO,EAAE,QAAQ,CAAA;CAAE,CAAM;
|
|
1
|
+
{"version":3,"file":"useAction.d.ts","sourceRoot":"","sources":["../src/useAction.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,SAAS,GAAI,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,EAAE,UAAS,OAAO,CAAC;IAAE,OAAO,EAAE,QAAQ,CAAA;CAAE,CAAM;aAGxG,OAAO;WACT,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;YACrB;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE;CAoBhD,CAAA"}
|
package/dist/useAction.js
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
1
|
+
import { useRef, useState } from "react";
|
|
2
2
|
export const useAction = (fn, options = {}) => {
|
|
3
|
+
const requestId = useRef(0);
|
|
3
4
|
const [state, set_state] = useState({ loading: false });
|
|
4
5
|
const f = async (...args) => {
|
|
6
|
+
const currentRequestId = ++requestId.current;
|
|
5
7
|
set_state({ loading: true });
|
|
6
8
|
try {
|
|
7
9
|
const data = await fn(...args);
|
|
8
|
-
|
|
10
|
+
if (currentRequestId === requestId.current) {
|
|
11
|
+
set_state({ loading: false, data });
|
|
12
|
+
}
|
|
9
13
|
return data;
|
|
10
14
|
}
|
|
11
15
|
catch (error) {
|
|
12
16
|
options.onError?.(error);
|
|
13
|
-
|
|
17
|
+
if (currentRequestId === requestId.current) {
|
|
18
|
+
set_state({ loading: false, error: error instanceof Error ? { code: 'error', message: error.message } : { code: 'error', message: String(error) } });
|
|
19
|
+
}
|
|
14
20
|
}
|
|
15
21
|
};
|
|
16
22
|
return Object.assign(f, state);
|
package/dist/useAction.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAction.js","sourceRoot":"","sources":["../src/useAction.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"useAction.js","sourceRoot":"","sources":["../src/useAction.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAExC,MAAM,CAAC,MAAM,SAAS,GAAG,CAA6C,EAAK,EAAE,UAA0C,EAAE,EAAE,EAAE;IACzH,MAAM,SAAS,GAAG,MAAM,CAAC,CAAC,CAAC,CAAA;IAC3B,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,GAAG,QAAQ,CAIhC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAA;IAEtB,MAAM,CAAC,GAAG,KAAK,EAAE,GAAG,IAAS,EAAE,EAAE;QAC7B,MAAM,gBAAgB,GAAG,EAAE,SAAS,CAAC,OAAO,CAAA;QAC5C,SAAS,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAA;QAC5B,IAAI,CAAC;YACD,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,GAAG,IAAI,CAAC,CAAA;YAC9B,IAAI,gBAAgB,KAAK,SAAS,CAAC,OAAO,EAAE,CAAC;gBACzC,SAAS,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;YACvC,CAAC;YACD,OAAO,IAAI,CAAA;QACf,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAA;YACxB,IAAI,gBAAgB,KAAK,SAAS,CAAC,OAAO,EAAE,CAAC;gBACzC,SAAS,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,CAAA;YACxJ,CAAC;QACL,CAAC;IACL,CAAC,CAAA;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,CAAM,EAAE,KAAK,CAAC,CAAA;AACvC,CAAC,CAAA"}
|
package/dist/useObservable.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Observable, BehaviorSubject } from "rxjs";
|
|
2
2
|
export type MaybeFunction<T> = T | (() => T);
|
|
3
|
-
type
|
|
3
|
+
type Source<T> = BehaviorSubject<T> | Observable<T>;
|
|
4
|
+
type ObservableSource<T> = MaybeFunction<Source<T>> | undefined;
|
|
4
5
|
export declare function useObservable<T>(o: BehaviorSubject<T>): T;
|
|
5
6
|
export declare function useObservable<T>(o: ObservableSource<T>): T | undefined;
|
|
6
7
|
export declare function useObservable<T>(o: ObservableSource<T>, default_value: T): T;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useObservable.d.ts","sourceRoot":"","sources":["../src/useObservable.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,eAAe,
|
|
1
|
+
{"version":3,"file":"useObservable.d.ts","sourceRoot":"","sources":["../src/useObservable.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAO,MAAM,MAAM,CAAC;AAGxD,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAA;AAE5C,KAAK,MAAM,CAAC,CAAC,IAAI,eAAe,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAA;AACnD,KAAK,gBAAgB,CAAC,CAAC,IAAI,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,SAAS,CAAA;AAU/D,wBAAgB,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;AAC1D,wBAAgB,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,SAAS,CAAA;AACvE,wBAAgB,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,GAAG,CAAC,CAAA"}
|
package/dist/useObservable.js
CHANGED
|
@@ -1,22 +1,28 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from "react";
|
|
2
|
-
import { Observable, BehaviorSubject, tap
|
|
2
|
+
import { Observable, BehaviorSubject, tap } from "rxjs";
|
|
3
3
|
import { skip } from "rxjs/operators";
|
|
4
|
+
const isBehaviorSubject = (source) => {
|
|
5
|
+
return typeof source?.getValue === 'function';
|
|
6
|
+
};
|
|
7
|
+
const hasPipe = (source) => {
|
|
8
|
+
return typeof source?.pipe === 'function';
|
|
9
|
+
};
|
|
4
10
|
export function useObservable(o, default_value) {
|
|
5
|
-
const
|
|
6
|
-
const source = o
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
11
|
+
const lazySource = useRef(undefined);
|
|
12
|
+
const source = typeof o === 'function'
|
|
13
|
+
? lazySource.current ?? (lazySource.current = o())
|
|
14
|
+
: o;
|
|
15
|
+
const prev = useRef(source);
|
|
16
|
+
const [v, s] = useState(() => isBehaviorSubject(source) ? source.getValue() : default_value);
|
|
10
17
|
useEffect(() => {
|
|
11
|
-
const diff = prev.current !==
|
|
12
|
-
prev.current =
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
catch (e) { }
|
|
18
|
+
const diff = prev.current !== source;
|
|
19
|
+
prev.current = source;
|
|
20
|
+
if (!hasPipe(source))
|
|
21
|
+
return;
|
|
22
|
+
const subscription = source.pipe(skip(isBehaviorSubject(source) && !diff ? 1 : 0), tap(s)).subscribe();
|
|
23
|
+
return () => {
|
|
24
|
+
subscription.unsubscribe();
|
|
25
|
+
};
|
|
20
26
|
}, typeof o === 'function' ? [] : [o]);
|
|
21
27
|
return v;
|
|
22
28
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useObservable.js","sourceRoot":"","sources":["../src/useObservable.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,GAAG,EAAE,
|
|
1
|
+
{"version":3,"file":"useObservable.js","sourceRoot":"","sources":["../src/useObservable.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC;AACxD,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAOtC,MAAM,iBAAiB,GAAG,CAAI,MAA6B,EAAgC,EAAE;IACzF,OAAO,OAAQ,MAAkD,EAAE,QAAQ,KAAK,UAAU,CAAA;AAC9F,CAAC,CAAA;AAED,MAAM,OAAO,GAAG,CAAI,MAA6B,EAAuB,EAAE;IACtE,OAAO,OAAQ,MAA6C,EAAE,IAAI,KAAK,UAAU,CAAA;AACrF,CAAC,CAAA;AAMD,MAAM,UAAU,aAAa,CAAI,CAAsB,EAAE,aAAiB;IACtE,MAAM,UAAU,GAAG,MAAM,CAAwB,SAAS,CAAC,CAAA;IAC3D,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,UAAU;QAClC,CAAC,CAAC,UAAU,CAAC,OAAO,IAAI,CAAC,UAAU,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC;QAClD,CAAC,CAAC,CAAC,CAAA;IACP,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,CAAA;IAC3B,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,QAAQ,CAAgB,GAAG,EAAE,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAA;IAE3G,SAAS,CAAC,GAAG,EAAE;QACX,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,KAAK,MAAM,CAAA;QACpC,IAAI,CAAC,OAAO,GAAG,MAAM,CAAA;QAErB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;YAAE,OAAM;QAE5B,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,CAC5B,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAChD,GAAG,CAAC,CAAC,CAAC,CACT,CAAC,SAAS,EAAE,CAAA;QACb,OAAO,GAAG,EAAE;YACR,YAAY,CAAC,WAAW,EAAE,CAAA;QAC9B,CAAC,CAAA;IACL,CAAC,EAAE,OAAO,CAAC,KAAK,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAEtC,OAAO,CAAC,CAAA;AACZ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,68 +1,73 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
"name": "@livequery/react",
|
|
3
|
+
"repository": {
|
|
4
|
+
"url": "https://github.com/livequery/react"
|
|
5
|
+
},
|
|
6
|
+
"type": "module",
|
|
7
|
+
"version": "2.0.135",
|
|
8
|
+
"description": "",
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"default": "./dist/index.js"
|
|
5
16
|
},
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
"types": "./dist/index.d.ts",
|
|
11
|
-
"exports": {
|
|
12
|
-
".": {
|
|
13
|
-
"types": "./dist/index.d.ts",
|
|
14
|
-
"import": "./dist/index.js",
|
|
15
|
-
"default": "./dist/index.js"
|
|
16
|
-
},
|
|
17
|
-
"./createContextFromHook": {
|
|
18
|
-
"types": "./dist/createContextFromHook.d.ts",
|
|
19
|
-
"import": "./dist/createContextFromHook.js",
|
|
20
|
-
"default": "./dist/createContextFromHook.js"
|
|
21
|
-
},
|
|
22
|
-
"./LivequeryClientContext": {
|
|
23
|
-
"types": "./dist/LivequeryClientContext.d.ts",
|
|
24
|
-
"import": "./dist/LivequeryClientContext.js",
|
|
25
|
-
"default": "./dist/LivequeryClientContext.js"
|
|
26
|
-
},
|
|
27
|
-
"./useAction": {
|
|
28
|
-
"types": "./dist/useAction.d.ts",
|
|
29
|
-
"import": "./dist/useAction.js",
|
|
30
|
-
"default": "./dist/useAction.js"
|
|
31
|
-
},
|
|
32
|
-
"./useCollection": {
|
|
33
|
-
"types": "./dist/useCollection.d.ts",
|
|
34
|
-
"import": "./dist/useCollection.js",
|
|
35
|
-
"default": "./dist/useCollection.js"
|
|
36
|
-
},
|
|
37
|
-
"./useDocument": {
|
|
38
|
-
"types": "./dist/useDocument.d.ts",
|
|
39
|
-
"import": "./dist/useDocument.js",
|
|
40
|
-
"default": "./dist/useDocument.js"
|
|
41
|
-
},
|
|
42
|
-
"./useObservable": {
|
|
43
|
-
"types": "./dist/useObservable.d.ts",
|
|
44
|
-
"import": "./dist/useObservable.js",
|
|
45
|
-
"default": "./dist/useObservable.js"
|
|
46
|
-
}
|
|
17
|
+
"./createContextFromHook": {
|
|
18
|
+
"types": "./dist/createContextFromHook.d.ts",
|
|
19
|
+
"import": "./dist/createContextFromHook.js",
|
|
20
|
+
"default": "./dist/createContextFromHook.js"
|
|
47
21
|
},
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
"@types/react": "^19.2.14"
|
|
22
|
+
"./LivequeryClientContext": {
|
|
23
|
+
"types": "./dist/LivequeryClientContext.d.ts",
|
|
24
|
+
"import": "./dist/LivequeryClientContext.js",
|
|
25
|
+
"default": "./dist/LivequeryClientContext.js"
|
|
53
26
|
},
|
|
54
|
-
"
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
27
|
+
"./useAction": {
|
|
28
|
+
"types": "./dist/useAction.d.ts",
|
|
29
|
+
"import": "./dist/useAction.js",
|
|
30
|
+
"default": "./dist/useAction.js"
|
|
58
31
|
},
|
|
59
|
-
"
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
"build:prod": "bun run build",
|
|
64
|
-
"build:watch": "bunx tsc -p tsconfig.build.json --watch"
|
|
32
|
+
"./useCollection": {
|
|
33
|
+
"types": "./dist/useCollection.d.ts",
|
|
34
|
+
"import": "./dist/useCollection.js",
|
|
35
|
+
"default": "./dist/useCollection.js"
|
|
65
36
|
},
|
|
66
|
-
"
|
|
67
|
-
|
|
37
|
+
"./useDocument": {
|
|
38
|
+
"types": "./dist/useDocument.d.ts",
|
|
39
|
+
"import": "./dist/useDocument.js",
|
|
40
|
+
"default": "./dist/useDocument.js"
|
|
41
|
+
},
|
|
42
|
+
"./useObservable": {
|
|
43
|
+
"types": "./dist/useObservable.d.ts",
|
|
44
|
+
"import": "./dist/useObservable.js",
|
|
45
|
+
"default": "./dist/useObservable.js"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"files": [
|
|
49
|
+
"dist/**/*"
|
|
50
|
+
],
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@livequery/client": "^2.0.135",
|
|
53
|
+
"@types/bun": "^1.3.14",
|
|
54
|
+
"@types/react": "^19.2.14",
|
|
55
|
+
"@types/react-test-renderer": "^19.1.0",
|
|
56
|
+
"react-test-renderer": "^19.2.6"
|
|
57
|
+
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"@livequery/client": "^2.0.135",
|
|
60
|
+
"react": "^19.2.5",
|
|
61
|
+
"rxjs": "^7.8.2"
|
|
62
|
+
},
|
|
63
|
+
"scripts": {
|
|
64
|
+
"clean": "rm -rf dist",
|
|
65
|
+
"build:types": "bunx tsc -p tsconfig.build.json",
|
|
66
|
+
"build": "bun run clean && bun run build:types",
|
|
67
|
+
"build:prod": "bun run build",
|
|
68
|
+
"test": "bun test",
|
|
69
|
+
"build:watch": "bunx tsc -p tsconfig.build.json --watch"
|
|
70
|
+
},
|
|
71
|
+
"author": "",
|
|
72
|
+
"license": "ISC"
|
|
68
73
|
}
|