@raf-query/react 0.1.1 → 0.1.3
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 +13 -1
- package/README.md +15 -1
- package/dist/client.d.mts +5 -0
- package/dist/client.mjs +7 -0
- 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,10 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.3](https://github.com/KATT/raf-query/compare/0.1.2...0.1.3) (2026-10-12)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
* **core:** cancel requests nobody reads anymore ([#27](https://github.com/KATT/raf-query/issues/27)) ([c3c1e4b](https://github.com/KATT/raf-query/commit/c3c1e4bb9250164b6df17d7d4b7bd4043f908961))
|
|
8
|
+
|
|
9
|
+
## [0.1.2](https://github.com/KATT/raf-query/compare/0.1.1...0.1.2) (2026-10-12)
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
- add isFetching(filter?) and useIsFetching(filter?) ([#25](https://github.com/KATT/raf-query/issues/25)) ([70c3b98](https://github.com/KATT/raf-query/commit/70c3b981027f717b1d619d5a2b23822f685c032a))
|
|
14
|
+
|
|
3
15
|
## [0.1.1](https://github.com/KATT/raf-query/compare/0.1.0...0.1.1) (2026-10-12)
|
|
4
16
|
|
|
5
17
|
### Features
|
|
6
18
|
|
|
7
|
-
|
|
19
|
+
- **core:** add predicate to QueryFilter ([#24](https://github.com/KATT/raf-query/issues/24)) ([c0b9c18](https://github.com/KATT/raf-query/commit/c0b9c1829cb3b6446f765b151e520f0195af5276))
|
|
8
20
|
|
|
9
21
|
## [0.1.0](https://github.com/KATT/raf-query/compare/0.0.19...0.1.0) (2026-10-12)
|
|
10
22
|
|
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:
|
|
@@ -475,7 +485,7 @@ raf-query's cache is written in [Effect](https://effect.website): `QueryClient`
|
|
|
475
485
|
- A `queryFn` can return an `Effect`. Cancelling the request interrupts it.
|
|
476
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.
|
|
477
487
|
- `retry` accepts a `Schedule` as well as a count or predicate.
|
|
478
|
-
- `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.
|
|
479
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.
|
|
480
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.
|
|
481
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`.
|
|
@@ -612,6 +622,7 @@ Both render nothing on the server, since the server's cache isn't the browser's.
|
|
|
612
622
|
| `remove(factory, input)` / `remove(filter)` | Remove entries and abort their requests. [Filters](#filters) |
|
|
613
623
|
| `effect` | The [Effect](#effect) `QueryCache` behind the client |
|
|
614
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? }` |
|
|
615
626
|
| `getAll()`, `getQuery(factory, input?)` | Cache entries, for devtools and integrations |
|
|
616
627
|
| `clear()` | Drop every entry and abort in-flight requests |
|
|
617
628
|
| `mount()` | Start window-focus and reconnect refetching. The providers call it |
|
|
@@ -633,6 +644,8 @@ Query options, accepted by `queryOptions`, `infiniteQueryOptions` and `config.de
|
|
|
633
644
|
|
|
634
645
|
In the browser, retries pause while `navigator.onLine` is `false`: after the retry delay, the next attempt waits for the window's `online` event. The request stays fetching meanwhile, and cancelling it (`remove`, `clear`, a restart) stops the wait. The first attempt runs regardless.
|
|
635
646
|
|
|
647
|
+
When the last reader of a query unsubscribes during a background refetch, a `queryFn` that read its `signal` (or returned an Effect) is aborted: the entry keeps its data and goes back to how it was before the request, and `fetch` callers waiting on it get the cached data. Other requests, including every first load (a render may be suspended on it), finish so their result is cached, but don't retry unless a reader comes back. Requests nothing has subscribed to yet, like a loader's `prefetch`, keep running.
|
|
648
|
+
|
|
636
649
|
`@raf-query/core`:
|
|
637
650
|
|
|
638
651
|
| Export | Use |
|
|
@@ -663,6 +676,7 @@ In the browser, retries pause while `navigator.onLine` is `false`: after the ret
|
|
|
663
676
|
| `useQueryClient()` | Read the client |
|
|
664
677
|
| `useQuery(factory, handle, options?)` | `[Thenable<Data>, controls]`; for paged factories `Data` is `{ pages, pageParams }` and controls include paging |
|
|
665
678
|
| `useWaterfall(factory, input)` | Start a query during render and return its handle |
|
|
679
|
+
| `useIsFetching(filter?)` | How many queries have a request in flight, re-rendering when it changes; for global loading indicators |
|
|
666
680
|
|
|
667
681
|
## Development
|
|
668
682
|
|
package/dist/client.d.mts
CHANGED
|
@@ -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
|
@@ -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/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.3",
|
|
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.3",
|
|
44
44
|
"effect": "^4.0.1"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|