@livequery/react 2.0.134 → 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 CHANGED
@@ -2,18 +2,7 @@
2
2
 
3
3
  Thin React bindings for `@livequery/client`.
4
4
 
5
- This repository is the React bindings library package, not an application. Changes here should preserve reusable hook behavior unless a task explicitly targets a breaking change.
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
- - `useGlobalValue`
26
+ - `useAction`
38
27
  - `createContextFromHook`
39
28
 
40
- ## Core setup
29
+ ## Recommended App Shape
41
30
 
42
- `useCollection` and `useDocument` read the active `LivequeryClient` instance from `LivequeryClientProvider`.
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 client={client}>
43
+ <LivequeryClientProvider core={client}>
55
44
  {children}
56
45
  </LivequeryClientProvider>
57
46
  )
58
47
  }
59
48
  ```
60
49
 
61
- ## useCollection
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` creates a `LivequeryCollection`, initializes it when `ref` is available, and returns the collection instance.
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
- Notes:
126
+ Behavior notes:
92
127
 
93
- - `ref` can be falsy. In that case initialization is skipped.
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
- - To render stream values, subscribe with `useObservable`.
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
- ## useDocument
136
+ `useDocument<T>(ref, options)` is a document-focused convenience wrapper over `useCollection()`.
98
137
 
99
- `useDocument` is a small convenience wrapper built on top of `LivequeryCollection`. It initializes a collection for a single document path and returns `[document, loading]`.
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
- ## useObservable
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
- `useObservable` bridges an RxJS `Observable` or `BehaviorSubject` into React state.
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
- You can also pass a function when the observable should be resolved lazily.
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
- import { LivequeryClient } from '@livequery/client'
144
- import { useGlobalValue } from '@livequery/react'
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
- ## createContextFromHook
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
- In practice, it turns this idea:
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
- - "I have some setup logic that depends on provider props"
162
- - "I want descendants to read the computed result through a hook"
196
+ ## `useAction`
163
197
 
164
- into a reusable pattern.
198
+ `useAction(fn, options)` wraps an async function and attaches action state to the returned callable.
165
199
 
166
- The helper takes one function `fn(props) => value` and returns a tuple:
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
- - `useValue`: reads the current context value
169
- - `Provider`: receives props, calls `fn(props)`, and stores the result in context
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
- - creating the context object
178
- - writing a custom consumer hook
179
- - writing a provider that computes and passes `value`
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
- It is especially useful when you want an API that reads like a hook-based service locator, but stays explicit through React providers.
212
+ return (
213
+ <button disabled={saveAction.loading} onClick={() => void saveAction()}>
214
+ {saveAction.loading ? 'Saving...' : 'Save'}
215
+ </button>
216
+ )
217
+ }
218
+ ```
182
219
 
183
- The `LivequeryClientProvider` and `useLivequeryClient` pair in this package is built from this helper.
220
+ The returned function has these state fields:
184
221
 
185
- ### Mental model
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
- You can think of it as turning a factory into two coordinated pieces:
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
- 1. A provider-side adapter: `Provider(props)` computes a value from props.
190
- 2. A consumer-side hook: `useValue()` reads that computed value from the nearest provider.
228
+ ## `createContextFromHook`
191
229
 
192
- So instead of manually writing both pieces every time, you derive them from one source function.
230
+ `createContextFromHook(fn)` derives a provider and a consumer hook from one factory function.
193
231
 
194
- ### Example
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
- ### What happens internally
255
+ The helper returns:
218
256
 
219
- The generated provider receives props plus `children`, calls your factory with those props, and pushes the returned value into a private React context.
257
+ - `useValue`: reads the current context value
258
+ - `Provider`: receives props, calls `fn(props)`, and stores the returned value in context
220
259
 
221
- The generated hook simply reads that context and returns the value as type `R`.
260
+ Behavior notes:
222
261
 
223
- This is why the consumer side feels like a plain hook even though the data flow is still standard React context under the hood.
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
- ### Good use cases
266
+ `LivequeryClientProvider` and `useLivequeryClient` are built with this helper.
226
267
 
227
- - exposing a configured client instance such as `LivequeryClient`
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
- ### Important behavior notes
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
- - The provider recomputes the value on every render because it directly calls `fn(props)`.
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
- ### Relationship to plain context
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
- If you wrote this manually, the shape would be:
287
+ ## Build
241
288
 
242
- 1. create a context
243
- 2. write `useX()` that calls `useContext`
244
- 3. write `XProvider` that computes a value from props
245
- 4. pass that value to the provider
289
+ ```bash
290
+ bun run build
291
+ ```
246
292
 
247
- `createContextFromHook` compresses those four steps into one helper and keeps the consuming API ergonomic.
293
+ Build output is published from `dist/` and exposed through the package `exports` field.
248
294
 
249
- ## Build
295
+ ## Test
250
296
 
251
297
  ```bash
252
- bun run build
298
+ bun test
253
299
  ```
254
300
 
255
- Build output is published from `dist/` and exposed through the package `exports` field.
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,KAczC,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,OAAO,CACzF,CAAA"}
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
- return useContext(ctx);
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,OAAO,UAAU,CAAC,GAAG,CAAM,CAAA;IAC/B,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"}
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"}
@@ -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;aAExG,OAAO;WACT,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;YACrB;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE;CAehD,CAAA"}
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
- set_state({ loading: false, data });
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
- set_state({ loading: false, error: error instanceof Error ? { code: 'error', message: error.message } : { code: 'error', message: String(error) } });
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);
@@ -1 +1 @@
1
- {"version":3,"file":"useAction.js","sourceRoot":"","sources":["../src/useAction.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAEhC,MAAM,CAAC,MAAM,SAAS,GAAG,CAA6C,EAAK,EAAE,UAA0C,EAAE,EAAE,EAAE;IACzH,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,SAAS,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAA;QAC5B,IAAI,CAAC;YACD,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,GAAG,IAAI,CAAC,CAAA;YAC9B,SAAS,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;YACnC,OAAO,IAAI,CAAA;QACf,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAA;YACxB,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;QACxJ,CAAC;IACL,CAAC,CAAA;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,CAAM,EAAE,KAAK,CAAC,CAAA;AACvC,CAAC,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"}
@@ -1,6 +1,7 @@
1
1
  import { Observable, BehaviorSubject } from "rxjs";
2
2
  export type MaybeFunction<T> = T | (() => T);
3
- type ObservableSource<T> = MaybeFunction<BehaviorSubject<T> | Observable<T>> | undefined;
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,EAAc,MAAM,MAAM,CAAC;AAG/D,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAA;AAE5C,KAAK,gBAAgB,CAAC,CAAC,IAAI,aAAa,CAAC,eAAe,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG,SAAS,CAAA;AAExF,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"}
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"}
@@ -1,22 +1,28 @@
1
1
  import { useEffect, useRef, useState } from "react";
2
- import { Observable, BehaviorSubject, tap, EMPTY } from "rxjs";
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 prev = useRef(o);
6
- const source = o || EMPTY;
7
- const isBehaviorSubject = typeof o == 'object' && typeof source.getValue === 'function';
8
- const dfv = isBehaviorSubject ? source.getValue() : default_value;
9
- const [v, s] = useState(dfv);
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 !== o;
12
- prev.current = o;
13
- try {
14
- const subscription = source.pipe(skip(isBehaviorSubject && !diff ? 1 : 0), tap(s)).subscribe();
15
- return () => {
16
- subscription.unsubscribe();
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,KAAK,EAAE,MAAM,MAAM,CAAC;AAC/D,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAUtC,MAAM,UAAU,aAAa,CAAI,CAAmB,EAAE,aAAiB;IACnE,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,CAAA;IACtB,MAAM,MAAM,GAAG,CAA8B,IAAI,KAAK,CAAA;IACtD,MAAM,iBAAiB,GAAE,OAAO,CAAC,IAAI,QAAQ,IAAK,OAAO,MAAM,CAAC,QAAQ,KAAK,UAAU,CAAA;IACvF,MAAM,GAAG,GAAG,iBAAiB,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,aAAa,CAAA;IACjE,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,QAAQ,CAAgB,GAAG,CAAC,CAAA;IAC3C,SAAS,CAAC,GAAG,EAAE;QACX,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,KAAK,CAAC,CAAA;QAC/B,IAAI,CAAC,OAAO,GAAG,CAAC,CAAA;QAChB,IAAI,CAAC;YACD,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,CAC5B,IAAI,CAAC,iBAAiB,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EACxC,GAAG,CAAC,CAAC,CAAC,CACT,CAAC,SAAS,EAAE,CAAA;YACb,OAAO,GAAG,EAAE;gBACR,YAAY,CAAC,WAAW,EAAE,CAAA;YAC9B,CAAC,CAAA;QACL,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;IACnB,CAAC,EAAE,OAAO,CAAC,KAAK,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAEtC,OAAO,CAAC,CAAA;AACZ,CAAC"}
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
@@ -4,7 +4,7 @@
4
4
  "url": "https://github.com/livequery/react"
5
5
  },
6
6
  "type": "module",
7
- "version": "2.0.134",
7
+ "version": "2.0.135",
8
8
  "description": "",
9
9
  "main": "./dist/index.js",
10
10
  "types": "./dist/index.d.ts",
@@ -49,18 +49,23 @@
49
49
  "dist/**/*"
50
50
  ],
51
51
  "devDependencies": {
52
- "@types/react": "^19.2.14"
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"
53
57
  },
54
58
  "peerDependencies": {
55
- "@livequery/client": "^2.0.134",
56
- "rxjs": "^7.8.2",
57
- "react": "^19.2.5"
59
+ "@livequery/client": "^2.0.135",
60
+ "react": "^19.2.5",
61
+ "rxjs": "^7.8.2"
58
62
  },
59
63
  "scripts": {
60
64
  "clean": "rm -rf dist",
61
65
  "build:types": "bunx tsc -p tsconfig.build.json",
62
66
  "build": "bun run clean && bun run build:types",
63
67
  "build:prod": "bun run build",
68
+ "test": "bun test",
64
69
  "build:watch": "bunx tsc -p tsconfig.build.json --watch"
65
70
  },
66
71
  "author": "",