@raf-query/react 0.3.0 → 0.3.1

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 CHANGED
@@ -1,14 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.1](https://github.com/KATT/raf-query/compare/0.3.0...0.3.1) (2026-10-13)
4
+
5
+ * teach avoiding dependent queries; frame prefetch vs ensure as loading UX ([#62](https://github.com/KATT/raf-query/issues/62)) ([ec62760](https://github.com/KATT/raf-query/commit/ec627609cbbee9fa4ff0cf568f69964341e27daa))
6
+
3
7
  ## [0.3.0](https://github.com/KATT/raf-query/compare/0.2.8...0.3.0) (2026-10-13)
4
8
 
5
9
  ### ⚠ BREAKING CHANGES
6
10
 
7
- * RSC apps hydrate only through HydrationBoundary; QueryProvider moves to /rsc ([#61](https://github.com/KATT/raf-query/issues/61))
11
+ - RSC apps hydrate only through HydrationBoundary; QueryProvider moves to /rsc ([#61](https://github.com/KATT/raf-query/issues/61))
8
12
 
9
13
  ### Features
10
14
 
11
- * RSC apps hydrate only through HydrationBoundary; QueryProvider moves to /rsc ([#61](https://github.com/KATT/raf-query/issues/61)) ([772de79](https://github.com/KATT/raf-query/commit/772de796adfdf267a9184fd75624ff30ae53f4fc)), references [#59](https://github.com/KATT/raf-query/issues/59) [#59](https://github.com/KATT/raf-query/issues/59) [#59](https://github.com/KATT/raf-query/issues/59) [#59](https://github.com/KATT/raf-query/issues/59)
15
+ - RSC apps hydrate only through HydrationBoundary; QueryProvider moves to /rsc ([#61](https://github.com/KATT/raf-query/issues/61)) ([772de79](https://github.com/KATT/raf-query/commit/772de796adfdf267a9184fd75624ff30ae53f4fc)), references [#59](https://github.com/KATT/raf-query/issues/59) [#59](https://github.com/KATT/raf-query/issues/59) [#59](https://github.com/KATT/raf-query/issues/59) [#59](https://github.com/KATT/raf-query/issues/59)
12
16
 
13
17
  ## [0.2.8](https://github.com/KATT/raf-query/compare/0.2.7...0.2.8) (2026-10-13)
14
18
 
package/README.md CHANGED
@@ -124,7 +124,7 @@ All three verbs live on the query client and accept a factory plus its input.
124
124
  | --------------------------------- | ----------------------------- | ------- | ---------------------------------------------------------------------- |
125
125
  | `qc.prefetch(factory, input)` | `QueryHandle` | No | Data the route can render without; it streams in |
126
126
  | `await qc.ensure(factory, input)` | `Promise<EnsuredQueryHandle>` | Yes | Data the route must not render without; `useQuery` returns it directly |
127
- | `await qc.fetch(factory, input)` | `Promise<Data>` | Yes | Rendering in a server component, or building another input |
127
+ | `await qc.fetch(factory, input)` | `Promise<Data>` | Yes | Rendering data in a server component |
128
128
 
129
129
  ```tsx
130
130
  // TanStack Start
@@ -139,6 +139,13 @@ export const Route = createFileRoute("/posts/$slug")({
139
139
  });
140
140
  ```
141
141
 
142
+ `prefetch` and `ensure` start the request at the same moment, so neither is faster. They differ in what the user sees while it's in flight:
143
+
144
+ - **`prefetch`: granular skeletons.** The navigation commits right away. Each part of the page that reads a query still loading suspends to its nearest Suspense boundary and fills in when its data streams in.
145
+ - **`ensure`: a blocked navigation.** The previous page stays on screen until the data is in. In TanStack Router, a route with a `pendingComponent` shows it once `pendingMs` (1 second by default) has passed, which makes a full-page skeleton.
146
+
147
+ Choose per query. `ensure` what the page can't lay out without, where a skeleton would only flash or shift (the post itself), and `prefetch` the rest (comments, sidebars, counts). Start every query before awaiting any of them, as the `prefetch` before the `await` above does, so a blocking query never delays the others.
148
+
142
149
  Await independent blocking queries together with `allKeyed`, which ships with raf-query. It's a ponyfill of the TC39 `Promise.allKeyed` proposal: a record of promises in, a promise of the same record out, rejecting on the first failure.
143
150
 
144
151
  ```ts
@@ -150,13 +157,74 @@ return await allKeyed({
150
157
  });
151
158
  ```
152
159
 
153
- Dependent inputs use `fetch`:
160
+ ### Avoid dependent queries
161
+
162
+ A query whose input comes from another query's result can't start until that one has finished. That's a waterfall: one extra round trip per level, on every navigation. Fix it where the data is shaped, so every query a route needs starts from what the URL already holds:
163
+
164
+ - **Put the input in the route.** If the page is about an organization, its id belongs in the path (`/orgs/$orgId`), not in a lookup on the current user.
165
+ - **Have the backend resolve it.** The server already knows the session and the relations, so an endpoint can return the child with its parent, or be keyed by what the client has (`GET /me/org` instead of `GET /me` then `GET /orgs/:id`).
166
+
167
+ <details>
168
+ <summary>Example: the current user's organization</summary>
169
+
170
+ A settings page needs the user and their organization, and the organization's id is a field of the user:
154
171
 
155
172
  ```ts
156
- const user = await qc.fetch(currentUserQuery);
157
- return { org: qc.prefetch(orgQuery, { orgId: user.orgId }) };
173
+ export const Route = createFileRoute("/settings")({
174
+ loader: async (args) => {
175
+ const qc = args.context.queryClient;
176
+ const user = await qc.fetch(currentUserQuery);
177
+ return {
178
+ user: qc.prefetch(currentUserQuery),
179
+ org: qc.prefetch(orgQuery, { orgId: user.orgId }),
180
+ };
181
+ },
182
+ });
158
183
  ```
159
184
 
185
+ With a 150 ms round trip, the organization can't arrive before 300 ms, and the navigation is blocked for the first 150 even though nothing on the page needed the user that early. Each query keyed by the organization (members, billing) would add another 150.
186
+
187
+ Fix it in the backend: the server can find the session's organization itself, so give it an endpoint and a query with no input.
188
+
189
+ ```ts
190
+ export function currentOrgQuery() {
191
+ return queryOptions({
192
+ queryKey: ["org", "current"],
193
+ queryFn: () => getCurrentOrg(),
194
+ });
195
+ }
196
+
197
+ export const Route = createFileRoute("/settings")({
198
+ loader: (args) => {
199
+ const qc = args.context.queryClient;
200
+ return {
201
+ user: qc.prefetch(currentUserQuery),
202
+ org: qc.prefetch(currentOrgQuery),
203
+ };
204
+ },
205
+ });
206
+ ```
207
+
208
+ Or fix it in the routes: links into the page come from places that already know the organization (the org switcher, a list of orgs), so they can put its id in the URL.
209
+
210
+ ```ts
211
+ export const Route = createFileRoute("/orgs/$orgId/settings")({
212
+ loader: (args) => {
213
+ const qc = args.context.queryClient;
214
+ return {
215
+ user: qc.prefetch(currentUserQuery),
216
+ org: qc.prefetch(orgQuery, { orgId: args.params.orgId }),
217
+ };
218
+ },
219
+ });
220
+ ```
221
+
222
+ Either way, both requests start together, and the page is ready after one round trip instead of two. Nothing waits, so each query can be a `prefetch` or an `ensure` purely for the loading experience you want.
223
+
224
+ </details>
225
+
226
+ When the data really can't be reshaped (a third-party API you don't control), the loader can `await qc.fetch(...)` the parent and build the input from its data, or a component can start the child with [`useWaterfall`](#render-time-inputs-usewaterfall). Both pay the extra round trip; the loader blocks the navigation on it, the component lets the rest of the page render first.
227
+
160
228
  Behavior:
161
229
 
162
230
  - Every verb dedupes against the cache. A fresh entry is reused; a stale entry is refetched in the background.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raf-query/react",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "React bindings for raf-query, a render-as-you-fetch query library",
5
5
  "keywords": [
6
6
  "query",
@@ -47,7 +47,7 @@
47
47
  }
48
48
  },
49
49
  "dependencies": {
50
- "@raf-query/core": "0.3.0"
50
+ "@raf-query/core": "0.3.1"
51
51
  },
52
52
  "peerDependencies": {
53
53
  "@tanstack/react-router": "^1.170.0",