@raf-query/react 0.1.0 → 0.1.2
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/CHANGELOG.md +18 -6
- package/README.md +31 -9
- package/dist/client.d.mts +6 -1
- package/dist/client.mjs +8 -1
- package/dist/devtools-panel.mjs +1 -1
- package/dist/index.d.mts +2 -1
- package/dist/index.mjs +2 -1
- package/dist/use-is-fetching.d.mts +15 -0
- package/dist/use-is-fetching.mjs +35 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,19 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.2](https://github.com/KATT/raf-query/compare/0.1.1...0.1.2) (2026-10-12)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
* add isFetching(filter?) and useIsFetching(filter?) ([#25](https://github.com/KATT/raf-query/issues/25)) ([70c3b98](https://github.com/KATT/raf-query/commit/70c3b981027f717b1d619d5a2b23822f685c032a))
|
|
8
|
+
|
|
9
|
+
## [0.1.1](https://github.com/KATT/raf-query/compare/0.1.0...0.1.1) (2026-10-12)
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
- **core:** add predicate to QueryFilter ([#24](https://github.com/KATT/raf-query/issues/24)) ([c0b9c18](https://github.com/KATT/raf-query/commit/c0b9c1829cb3b6446f765b151e520f0195af5276))
|
|
14
|
+
|
|
3
15
|
## [0.1.0](https://github.com/KATT/raf-query/compare/0.0.19...0.1.0) (2026-10-12)
|
|
4
16
|
|
|
5
17
|
### ⚠ BREAKING CHANGES
|
|
6
18
|
|
|
7
|
-
|
|
8
|
-
|
|
19
|
+
- Effect-native engine; core exposes only Effect APIs ([#23](https://github.com/KATT/raf-query/issues/23))
|
|
20
|
+
- **core:** make QueryObserver an Effect-style data type ([#29](https://github.com/KATT/raf-query/issues/29))
|
|
9
21
|
|
|
10
|
-
|
|
11
|
-
|
|
22
|
+
- **core:** make QueryObserver an Effect-style data type ([#29](https://github.com/KATT/raf-query/issues/29)) ([50fbb45](https://github.com/KATT/raf-query/commit/50fbb45bf3edcaa83673e2177894a76bdf76560b))
|
|
23
|
+
- Effect-native engine; core exposes only Effect APIs ([#23](https://github.com/KATT/raf-query/issues/23)) ([8e0bb09](https://github.com/KATT/raf-query/commit/8e0bb09a63d644ede053f443406a521ff2613a6f))
|
|
12
24
|
|
|
13
25
|
### Features
|
|
14
26
|
|
|
15
|
-
|
|
16
|
-
|
|
27
|
+
- **core:** stream SSR dehydration as Effect APIs; integrations become thin edges ([#30](https://github.com/KATT/raf-query/issues/30)) ([6b2b570](https://github.com/KATT/raf-query/commit/6b2b5703f5463c0f3805c233cf4ead90df9efb76)), references [#29](https://github.com/KATT/raf-query/issues/29)
|
|
28
|
+
- typed services for Effect queries; QueryClient layer and dispose() ([#31](https://github.com/KATT/raf-query/issues/31)) ([6aeaa3d](https://github.com/KATT/raf-query/commit/6aeaa3d356f6483654729620ac26762508eb8fd8))
|
|
17
29
|
|
|
18
30
|
## [0.0.19](https://github.com/KATT/raf-query/compare/0.0.18...0.0.19) (2026-10-12)
|
|
19
31
|
|
package/README.md
CHANGED
|
@@ -196,6 +196,16 @@ When the cache updates — refetch, invalidation, `setData` — the hook returns
|
|
|
196
196
|
|
|
197
197
|
Queries do not refetch on mount. They refetch when a loader starts them again, when invalidated, on window focus or reconnect while stale, on `refetch()`, or on `refetchInterval`.
|
|
198
198
|
|
|
199
|
+
For an app-wide indicator, `useIsFetching()` returns how many queries have a request in flight, background refetches included, and re-renders when that changes. Pass a filter to count only some: `useIsFetching({ queryKey: ["comments"] })`. It's `0` on the server and during hydration, so the indicator never appears in server HTML. Outside React, `qc.isFetching(filter?)` returns the same number.
|
|
200
|
+
|
|
201
|
+
```tsx
|
|
202
|
+
// Rendered once, in the root layout.
|
|
203
|
+
function GlobalProgress() {
|
|
204
|
+
const fetching = useIsFetching();
|
|
205
|
+
return fetching > 0 ? <ProgressBar /> : null;
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
199
209
|
### Paged queries
|
|
200
210
|
|
|
201
211
|
With an `infiniteQueryOptions` factory, `useQuery` returns the same tuple. The promise resolves to `{ pages, pageParams }`, and the controls add paging:
|
|
@@ -358,12 +368,22 @@ function EditTitle(props: { post: Post }) {
|
|
|
358
368
|
|
|
359
369
|
`updatePost` is a server function (or a Next.js server action). `await qc.invalidate(...)` resolves once every query currently being read has refetched, so the action's pending state lasts until the fresh data is on screen. For an error message, wrap the action in `useActionState` and return the caught error.
|
|
360
370
|
|
|
361
|
-
| Call | Effect
|
|
362
|
-
| ------------------------------------- |
|
|
363
|
-
| `qc.invalidate(factory, input)` | Mark one query stale and refetch it if it is being read
|
|
364
|
-
| `qc.invalidate(
|
|
365
|
-
| `qc.setData(factory, input, updater)` | Write data directly
|
|
366
|
-
| `qc.getData(factory, input)` | Read cached data, or `undefined`
|
|
371
|
+
| Call | Effect |
|
|
372
|
+
| ------------------------------------- | ------------------------------------------------------- |
|
|
373
|
+
| `qc.invalidate(factory, input)` | Mark one query stale and refetch it if it is being read |
|
|
374
|
+
| `qc.invalidate(filter)` | Same, for every query the [filter](#filters) selects |
|
|
375
|
+
| `qc.setData(factory, input, updater)` | Write data directly |
|
|
376
|
+
| `qc.getData(factory, input)` | Read cached data, or `undefined` |
|
|
377
|
+
|
|
378
|
+
### Filters
|
|
379
|
+
|
|
380
|
+
`invalidate`, `reset` and `remove` also take a filter, `{ queryKey?, exact?, predicate? }`. `queryKey` selects every query whose key starts with it, or only that key with `exact: true`; object segments match partially, so `{ queryKey: ["issues", { projectId: 3 }] }` selects every issue list in project 3. `predicate` gets each remaining `Query` and keeps the ones it returns `true` for: read its `key` and `handle.input`, or use the `Query` functions (`Query.getUnsafe(query)` for its state, `Query.observerCountUnsafe(query)`, `Query.isStaleUnsafe(query)`). Without `queryKey` every key matches, so `{}` selects the whole cache.
|
|
381
|
+
|
|
382
|
+
```ts
|
|
383
|
+
qc.remove({ predicate: (query) => Query.observerCountUnsafe(query) === 0 }); // everything not being read
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
`handle.input` is the input of the latest factory call for that entry. An entry that was only hydrated, and not yet read or started on this client, has `undefined` input, so match on `key` to include those.
|
|
367
387
|
|
|
368
388
|
Data can be `undefined` when the `queryFn`'s type allows it (`Promise<User | undefined>`); it's cached and reused like any other value, unlike in TanStack Query, where an `undefined` result is an error. So `getData` and an updater's `previous` return `undefined` both with no data and with `undefined` data; `Query.getUnsafe(qc.getQuery(factory, input)).hasData` tells them apart.
|
|
369
389
|
|
|
@@ -465,7 +485,7 @@ raf-query's cache is written in [Effect](https://effect.website): `QueryClient`
|
|
|
465
485
|
- A `queryFn` can return an `Effect`. Cancelling the request interrupts it.
|
|
466
486
|
- An Effect `queryFn`'s error type is the query's: `qc.effect.fetch(issueQuery, input)` fails with `IssueNotFound | Query.CancelledError`. `ErrorOf<TFactory>` names it; it's `unknown` for promises. A `queryFn` that throws instead of returning is a defect, and isn't retried.
|
|
467
487
|
- `retry` accepts a `Schedule` as well as a count or predicate.
|
|
468
|
-
- `qc.effect` has every verb as an Effect: `prefetch`, `ensure`, `fetch`, `invalidate`, `reset`, `remove`, `setData`, `getData` (an `Option`), `dehydrate`, `hydrate`. `watch` is a `Stream` of an entry's state, and `events` a `Stream` of cache events.
|
|
488
|
+
- `qc.effect` has every verb as an Effect: `prefetch`, `ensure`, `fetch`, `invalidate`, `reset`, `remove`, `setData`, `getData` (an `Option`), `isFetching` (a count), `dehydrate`, `hydrate`. `watch` is a `Stream` of an entry's state, and `events` a `Stream` of cache events.
|
|
469
489
|
- SSR streaming is Effect too, and the integrations are thin wrappers over it. `dehydrateStream(options)` (scoped) returns the cache's `initial` state and a `Stream` of `updates`: entries that start or settle later, each sent once, batched per microtask. The stream ends when the scope closes. `hydrateStream(stream)` hydrates each state as it arrives. `Stream.toReadableStream` / `Stream.fromReadableStream` carry it over a web stream. The `sent` option (a `Set` of hashes) makes separate `dehydrate` calls of one request skip entries already sent.
|
|
470
490
|
- Entries are a data type with module functions, as in Effect's own modules: `Query.fetch(query)`, `Query.refetch(query)`, `Query.invalidate(query)`, `Query.changes(query)`, and `Query.getUnsafe(query)` / `Query.subscribeUnsafe(query, listener)` for synchronous reads.
|
|
471
491
|
- `QueryObserver` is a data type too: one reader's view of a query, as `useQuery` sees it (the controls and the promise, with `whilePending`, `refetchInterval` and the focus / reconnect options). `QueryObserver.make(query, options)` creates one, and `QueryObserver.changes(observer)` streams its results; while it runs, the query counts as read and polls on `refetchInterval`.
|
|
@@ -598,10 +618,11 @@ Both render nothing on the server, since the server's cache isn't the browser's.
|
|
|
598
618
|
| `new QueryClient(config?)` | `config.defaults` (query options below), `config.onError`, `config.isServer`, `config.layer` ([services](#services)) |
|
|
599
619
|
| `prefetch`, `ensure`, `fetch` | [Start queries](#starting-queries) |
|
|
600
620
|
| `invalidate`, `setData`, `getData` | [Write and read the cache](#mutations) |
|
|
601
|
-
| `reset(factory, input)` / `reset(filter)` | Drop data and errors; read queries suspend and refetch. Resolves when they settle
|
|
602
|
-
| `remove(factory, input)` / `remove(filter)` | Remove entries and abort their requests
|
|
621
|
+
| `reset(factory, input)` / `reset(filter)` | Drop data and errors; read queries suspend and refetch. Resolves when they settle. [Filters](#filters) |
|
|
622
|
+
| `remove(factory, input)` / `remove(filter)` | Remove entries and abort their requests. [Filters](#filters) |
|
|
603
623
|
| `effect` | The [Effect](#effect) `QueryCache` behind the client |
|
|
604
624
|
| `subscribe(listener)` | Cache events: `added`, `updated`, `observers` (a reader subscribed or left), `removed`, and `failed` (with the `error`) |
|
|
625
|
+
| `isFetching(filter?)` | How many entries have a request in flight, optionally only those matching `{ queryKey, exact? }` |
|
|
605
626
|
| `getAll()`, `getQuery(factory, input?)` | Cache entries, for devtools and integrations |
|
|
606
627
|
| `clear()` | Drop every entry and abort in-flight requests |
|
|
607
628
|
| `mount()` | Start window-focus and reconnect refetching. The providers call it |
|
|
@@ -653,6 +674,7 @@ In the browser, retries pause while `navigator.onLine` is `false`: after the ret
|
|
|
653
674
|
| `useQueryClient()` | Read the client |
|
|
654
675
|
| `useQuery(factory, handle, options?)` | `[Thenable<Data>, controls]`; for paged factories `Data` is `{ pages, pageParams }` and controls include paging |
|
|
655
676
|
| `useWaterfall(factory, input)` | Start a query during render and return its handle |
|
|
677
|
+
| `useIsFetching(filter?)` | How many queries have a request in flight, re-rendering when it changes; for global loading indicators |
|
|
656
678
|
|
|
657
679
|
## Development
|
|
658
680
|
|
package/dist/client.d.mts
CHANGED
|
@@ -46,7 +46,7 @@ export declare class QueryClient<TServices = never> {
|
|
|
46
46
|
/**
|
|
47
47
|
* Mark queries stale and refetch the ones being read. Resolves once those
|
|
48
48
|
* refetches settle, so an action stays pending until fresh data is on
|
|
49
|
-
* screen. Takes a factory and its input, or a {@link
|
|
49
|
+
* screen. Takes a factory and its input, or a {@link QueryCache.Filter}.
|
|
50
50
|
*/
|
|
51
51
|
invalidate<TFactory extends QueryFactory<TServices>>(...target: QueryCache.Target<TFactory>): Promise<void>;
|
|
52
52
|
/**
|
|
@@ -69,6 +69,11 @@ export declare class QueryClient<TServices = never> {
|
|
|
69
69
|
getData<TFactory extends QueryFactory<TServices>>(factory: TFactory, ...args: InputArgs<TFactory>): DataOf<TFactory> | undefined;
|
|
70
70
|
/** Every entry, for devtools and integrations. */
|
|
71
71
|
getAll(): ReadonlyArray<Query.Query>;
|
|
72
|
+
/**
|
|
73
|
+
* How many entries have a request in flight: all of them, or those
|
|
74
|
+
* matching `filter`. For global loading indicators.
|
|
75
|
+
*/
|
|
76
|
+
isFetching(filter?: QueryCache.Filter): number;
|
|
72
77
|
/** Listen to entries being added, updated, and removed. */
|
|
73
78
|
subscribe(listener: (event: QueryCache.Event) => void): () => void;
|
|
74
79
|
/** Drop every entry and abort in-flight requests. */
|
package/dist/client.mjs
CHANGED
|
@@ -66,7 +66,7 @@ var QueryClient = class {
|
|
|
66
66
|
/**
|
|
67
67
|
* Mark queries stale and refetch the ones being read. Resolves once those
|
|
68
68
|
* refetches settle, so an action stays pending until fresh data is on
|
|
69
|
-
* screen. Takes a factory and its input, or a {@link
|
|
69
|
+
* screen. Takes a factory and its input, or a {@link QueryCache.Filter}.
|
|
70
70
|
*/
|
|
71
71
|
invalidate(...target) {
|
|
72
72
|
return Effect.runPromise(this.effect.invalidate(...target));
|
|
@@ -101,6 +101,13 @@ var QueryClient = class {
|
|
|
101
101
|
getAll() {
|
|
102
102
|
return Effect.runSync(this.effect.all);
|
|
103
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* How many entries have a request in flight: all of them, or those
|
|
106
|
+
* matching `filter`. For global loading indicators.
|
|
107
|
+
*/
|
|
108
|
+
isFetching(filter) {
|
|
109
|
+
return this.effect.isFetchingUnsafe(filter);
|
|
110
|
+
}
|
|
104
111
|
/** Listen to entries being added, updated, and removed. */
|
|
105
112
|
subscribe(listener) {
|
|
106
113
|
return this.effect.subscribeUnsafe(listener);
|
package/dist/devtools-panel.mjs
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { QueryClient, QueryClientConfig, dehydrate, hydrate } from "./client.mjs";
|
|
2
2
|
import { Settled, allKeyed } from "./all-keyed.mjs";
|
|
3
3
|
import { QueryClientProvider, useQueryClient } from "./provider.mjs";
|
|
4
|
+
import { useIsFetching } from "./use-is-fetching.mjs";
|
|
4
5
|
import { PagingControls, QueryControls, UseEnsuredQueryResult, UseQueryControls, UseQueryOptions, UseQueryResult, useQuery } from "./use-query.mjs";
|
|
5
6
|
import { useWaterfall } from "./use-waterfall.mjs";
|
|
6
7
|
export * from "@raf-query/core";
|
|
7
|
-
export { type PagingControls, QueryClient, type QueryClientConfig, QueryClientProvider, type QueryControls, type Settled, type UseEnsuredQueryResult, type UseQueryControls, type UseQueryOptions, type UseQueryResult, allKeyed, dehydrate, hydrate, useQuery, useQueryClient, useWaterfall };
|
|
8
|
+
export { type PagingControls, QueryClient, type QueryClientConfig, QueryClientProvider, type QueryControls, type Settled, type UseEnsuredQueryResult, type UseQueryControls, type UseQueryOptions, type UseQueryResult, allKeyed, dehydrate, hydrate, useIsFetching, useQuery, useQueryClient, useWaterfall };
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { allKeyed } from "./all-keyed.mjs";
|
|
2
2
|
import { QueryClient, dehydrate, hydrate } from "./client.mjs";
|
|
3
3
|
import { QueryClientProvider, useQueryClient } from "./provider.mjs";
|
|
4
|
+
import { useIsFetching } from "./use-is-fetching.mjs";
|
|
4
5
|
import { useQuery } from "./use-query.mjs";
|
|
5
6
|
import { useWaterfall } from "./use-waterfall.mjs";
|
|
6
7
|
export * from "@raf-query/core";
|
|
7
|
-
export { QueryClient, QueryClientProvider, allKeyed, dehydrate, hydrate, useQuery, useQueryClient, useWaterfall };
|
|
8
|
+
export { QueryClient, QueryClientProvider, allKeyed, dehydrate, hydrate, useIsFetching, useQuery, useQueryClient, useWaterfall };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { QueryCache } from "@raf-query/core";
|
|
2
|
+
//#region src/use-is-fetching.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* How many queries have a request in flight: all of them, or those matching
|
|
5
|
+
* `filter`. Re-renders when the count changes, for global loading
|
|
6
|
+
* indicators. It's `0` on the server and while hydrating, so the server's
|
|
7
|
+
* HTML and the browser's first render agree:
|
|
8
|
+
*
|
|
9
|
+
* ```tsx
|
|
10
|
+
* const fetching = useIsFetching();
|
|
11
|
+
* return fetching > 0 ? <ProgressBar /> : null;
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export declare function useIsFetching(filter?: QueryCache.Filter): number;
|
|
15
|
+
//#endregion
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useQueryClient } from "./provider.mjs";
|
|
3
|
+
import { useCallback, useSyncExternalStore } from "react";
|
|
4
|
+
//#region src/use-is-fetching.ts
|
|
5
|
+
function subscribeDeferred(client, onStoreChange) {
|
|
6
|
+
let scheduled = false;
|
|
7
|
+
return client.subscribe(() => {
|
|
8
|
+
if (scheduled) return;
|
|
9
|
+
scheduled = true;
|
|
10
|
+
queueMicrotask(() => {
|
|
11
|
+
scheduled = false;
|
|
12
|
+
onStoreChange();
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
const none = () => 0;
|
|
17
|
+
/**
|
|
18
|
+
* How many queries have a request in flight: all of them, or those matching
|
|
19
|
+
* `filter`. Re-renders when the count changes, for global loading
|
|
20
|
+
* indicators. It's `0` on the server and while hydrating, so the server's
|
|
21
|
+
* HTML and the browser's first render agree:
|
|
22
|
+
*
|
|
23
|
+
* ```tsx
|
|
24
|
+
* const fetching = useIsFetching();
|
|
25
|
+
* return fetching > 0 ? <ProgressBar /> : null;
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
function useIsFetching(filter) {
|
|
29
|
+
const client = useQueryClient();
|
|
30
|
+
const subscribe = useCallback((onStoreChange) => subscribeDeferred(client, onStoreChange), [client]);
|
|
31
|
+
const getSnapshot = () => client.isFetching(filter);
|
|
32
|
+
return useSyncExternalStore(subscribe, getSnapshot, none);
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
export { useIsFetching };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@raf-query/react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "React bindings for raf-query, a render-as-you-fetch query library",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"query",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"./tanstack-router": "./dist/tanstack-router.mjs"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@raf-query/core": "0.1.
|
|
43
|
+
"@raf-query/core": "0.1.2",
|
|
44
44
|
"effect": "^4.0.1"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|