@livequery/react 2.0.26 → 2.0.92

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 ADDED
@@ -0,0 +1,244 @@
1
+ # @livequery/react
2
+
3
+ Thin React bindings for `@livequery/core`.
4
+
5
+ This package provides a small set of hooks and helpers for wiring a `LivequeryCore` instance into a React app, subscribing to RxJS streams, and reading collection or document state from `@livequery/core`.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ bun add @livequery/react @livequery/core react rxjs
11
+ ```
12
+
13
+ Or with npm:
14
+
15
+ ```bash
16
+ npm install @livequery/react @livequery/core react rxjs
17
+ ```
18
+
19
+ ## Exports
20
+
21
+ - `LivequeryCoreProvider`
22
+ - `useLivequeryCore`
23
+ - `useCollection`
24
+ - `useDocument`
25
+ - `useObservable`
26
+ - `useGlobalValue`
27
+ - `createContextFromHook`
28
+
29
+ ## Core setup
30
+
31
+ `useCollection` and `useDocument` read the active `LivequeryCore` instance from `LivequeryCoreProvider`.
32
+
33
+ ```tsx
34
+ import { LivequeryCore } from '@livequery/core'
35
+ import { LivequeryCoreProvider } from '@livequery/react'
36
+
37
+ const core = new LivequeryCore({
38
+ endpoint: 'https://your-livequery-server'
39
+ })
40
+
41
+ export function AppProviders({ children }: { children: React.ReactNode }) {
42
+ return (
43
+ <LivequeryCoreProvider core={core}>
44
+ {children}
45
+ </LivequeryCoreProvider>
46
+ )
47
+ }
48
+ ```
49
+
50
+ ## useCollection
51
+
52
+ `useCollection` creates a `LivequeryCollection`, initializes it when `ref` is available, and returns the collection instance.
53
+
54
+ ```tsx
55
+ import { useCollection, useObservable } from '@livequery/react'
56
+
57
+ type Todo = {
58
+ _id: string
59
+ title: string
60
+ done: boolean
61
+ }
62
+
63
+ export function TodoList() {
64
+ const collection = useCollection<Todo>('todos', { lazy: false })
65
+ const items = useObservable(collection.items, [])
66
+ const loading = useObservable(collection.loading, false)
67
+
68
+ if (loading) return <p>Loading...</p>
69
+
70
+ return (
71
+ <ul>
72
+ {items.map((todo) => (
73
+ <li key={todo._id}>{todo.title}</li>
74
+ ))}
75
+ </ul>
76
+ )
77
+ }
78
+ ```
79
+
80
+ Notes:
81
+
82
+ - `ref` can be falsy. In that case initialization is skipped.
83
+ - The same hook call keeps one collection instance for the lifetime of the component.
84
+ - To render stream values, subscribe with `useObservable`.
85
+
86
+ ## useDocument
87
+
88
+ `useDocument` is a small convenience wrapper built on top of `LivequeryCollection`. It initializes a collection for a single document path and returns `[document, loading]`.
89
+
90
+ ```tsx
91
+ import { useDocument } from '@livequery/react'
92
+
93
+ type Todo = {
94
+ _id: string
95
+ title: string
96
+ done: boolean
97
+ }
98
+
99
+ export function TodoDetail({ id }: { id: string }) {
100
+ const [todo, loading] = useDocument<Todo>(`todos/${id}`)
101
+
102
+ if (loading) return <p>Loading...</p>
103
+ if (!todo) return <p>Not found</p>
104
+
105
+ return <h1>{todo.title}</h1>
106
+ }
107
+ ```
108
+
109
+ ## useObservable
110
+
111
+ `useObservable` bridges an RxJS `Observable` or `BehaviorSubject` into React state.
112
+
113
+ ```tsx
114
+ import { BehaviorSubject } from 'rxjs'
115
+ import { useObservable } from '@livequery/react'
116
+
117
+ const counter$ = new BehaviorSubject(0)
118
+
119
+ export function Counter() {
120
+ const value = useObservable(counter$, 0)
121
+ return <span>{value}</span>
122
+ }
123
+ ```
124
+
125
+ You can also pass a function when the observable should be resolved lazily.
126
+
127
+ ## useGlobalValue
128
+
129
+ `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.
130
+
131
+ ```tsx
132
+ import { LivequeryCore } from '@livequery/core'
133
+ import { useGlobalValue } from '@livequery/react'
134
+
135
+ export function useAppCore() {
136
+ return useGlobalValue('livequery-core', () => {
137
+ return new LivequeryCore({
138
+ endpoint: 'https://your-livequery-server'
139
+ })
140
+ })
141
+ }
142
+ ```
143
+
144
+ ## createContextFromHook
145
+
146
+ `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.
147
+
148
+ In practice, it turns this idea:
149
+
150
+ - "I have some setup logic that depends on provider props"
151
+ - "I want descendants to read the computed result through a hook"
152
+
153
+ into a reusable pattern.
154
+
155
+ The helper takes one function `fn(props) => value` and returns a tuple:
156
+
157
+ - `useValue`: reads the current context value
158
+ - `Provider`: receives props, calls `fn(props)`, and stores the result in context
159
+
160
+ That means consumers do not need to know about `React.createContext`, context objects, or provider value wiring. They only call a hook.
161
+
162
+ ### Why this helper is useful
163
+
164
+ Compared to creating context by hand, `createContextFromHook` removes the repetitive parts:
165
+
166
+ - creating the context object
167
+ - writing a custom consumer hook
168
+ - writing a provider that computes and passes `value`
169
+
170
+ It is especially useful when you want an API that reads like a hook-based service locator, but stays explicit through React providers.
171
+
172
+ The `LivequeryCoreProvider` and `useLivequeryCore` pair in this package is built from this helper.
173
+
174
+ ### Mental model
175
+
176
+ You can think of it as turning a factory into two coordinated pieces:
177
+
178
+ 1. A provider-side adapter: `Provider(props)` computes a value from props.
179
+ 2. A consumer-side hook: `useValue()` reads that computed value from the nearest provider.
180
+
181
+ So instead of manually writing both pieces every time, you derive them from one source function.
182
+
183
+ ### Example
184
+
185
+ ```tsx
186
+ import { createContextFromHook } from '@livequery/react'
187
+
188
+ const [useSession, SessionProvider] = createContextFromHook(
189
+ ({ token }: { token: string }) => ({ token })
190
+ )
191
+
192
+ function Child() {
193
+ const session = useSession()
194
+ return <div>{session.token}</div>
195
+ }
196
+
197
+ function App() {
198
+ return (
199
+ <SessionProvider token="abc123">
200
+ <Child />
201
+ </SessionProvider>
202
+ )
203
+ }
204
+ ```
205
+
206
+ ### What happens internally
207
+
208
+ The generated provider receives props plus `children`, calls your factory with those props, and pushes the returned value into a private React context.
209
+
210
+ The generated hook simply reads that context and returns the value as type `R`.
211
+
212
+ This is why the consumer side feels like a plain hook even though the data flow is still standard React context under the hood.
213
+
214
+ ### Good use cases
215
+
216
+ - exposing a configured client instance such as `LivequeryCore`
217
+ - deriving session or auth state from provider props
218
+ - wrapping feature-specific state that should be consumed through a single custom hook
219
+ - hiding context implementation details from package consumers
220
+
221
+ ### Important behavior notes
222
+
223
+ - The provider recomputes the value on every render because it directly calls `fn(props)`.
224
+ - 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.
225
+ - `createContextFromHook` does not add memoization by itself. If `fn(props)` is expensive, memoize inside `fn` or stabilize the incoming props.
226
+
227
+ ### Relationship to plain context
228
+
229
+ If you wrote this manually, the shape would be:
230
+
231
+ 1. create a context
232
+ 2. write `useX()` that calls `useContext`
233
+ 3. write `XProvider` that computes a value from props
234
+ 4. pass that value to the provider
235
+
236
+ `createContextFromHook` compresses those four steps into one helper and keeps the consuming API ergonomic.
237
+
238
+ ## Build
239
+
240
+ ```bash
241
+ bun run build
242
+ ```
243
+
244
+ Build output is published from `dist/` and exposed through the package `exports` field.
@@ -0,0 +1,16 @@
1
+ import { type PropsWithChildren } from "react";
2
+ /**
3
+ * Creates a React context pair from a hook-like factory.
4
+ *
5
+ * The returned tuple contains:
6
+ * - a consumer hook that reads the computed value from context
7
+ * - a provider component that calls `fn(props)` and places the result into context
8
+ *
9
+ * This pattern is useful when some shared state should feel like a normal hook at call
10
+ * sites, while still being configured once at the edge of a subtree.
11
+ *
12
+ * @param fn Factory that receives provider props and returns the context value.
13
+ * @returns A tuple of `[useValue, Provider]`.
14
+ */
15
+ export declare const createContextFromHook: <T, R>(fn: ((props: T) => R) | (() => R)) => [() => R, React.FC<PropsWithChildren<T>>];
16
+ //# sourceMappingURL=createContextFromHook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createContextFromHook.d.ts","sourceRoot":"","sources":["../src/createContextFromHook.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA6B,KAAK,iBAAiB,EAAE,MAAM,OAAO,CAAA;AAEzE;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,qBAAqB,GAAI,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,KAqBzC,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAC3E,CAAA"}
@@ -0,0 +1,6 @@
1
+ export * from './createContextFromHook';
2
+ export * from './useCollection';
3
+ export * from './useDocument';
4
+ export * from './useGlobalValue';
5
+ export * from './useObservable';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAA;AACvC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,kBAAkB,CAAA;AAChC,cAAc,iBAAiB,CAAA"}