You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: .changeset/skip-action-revalidation.md
+2-1Lines changed: 2 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -7,4 +7,5 @@ Add a new `future.unstable_skipActionRevalidation` future flag
7
7
- Currently, active loaders revalidate after any action, regardless of the result
8
8
- With this flag enabled, actions that return/throw a 4xx/5xx response status will no longer automatically revalidate
9
9
- This should reduce load on your server since it's rare that a 4xx/5xx should actually mutate any data
10
-
- If you need to revalidate after a 4xx/5xx result with this flag enabled, you can still do that via returning `true` from `shouldRevalidate` which now receives a new `unstable_actionStatus` argument
10
+
- If you need to revalidate after a 4xx/5xx result with this flag enabled, you can still do that via returning `true` from `shouldRevalidate`
11
+
-`shouldRevalidate` now also receives a new `unstable_actionStatus` argument alongside `actionResult` so you can make decision based on the status of the `action` response without having to encode it into the action data
Added 2 new options to the `staticHandler.query` method for use in Remix's Single Fetch implementation:
6
+
7
+
-`loadRouteIds`: An optional array of route IDs to load if you wish to load a subset of the matched routes (useful for fine-grained revalidation)
8
+
-`skipLoaderErrorBubbling`: Disable error bubbling on loader executions for single-fetch scenarios where the client-side router will handle the bubbling
Copy file name to clipboardExpand all lines: decisions/0003-data-strategy.md
+71-10Lines changed: 71 additions & 10 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -54,11 +54,11 @@ function dataStrategy({ matches }) {
54
54
}
55
55
```
56
56
57
-
⚠️ `defaultStrategy` was eliminated in favor of `match.handler`.
57
+
⚠️ `defaultStrategy` was eliminated in favor of `match.resolve`.
58
58
59
-
We also originally intended to expose a `type: 'loader' | 'action`' field as a way to presumably let them call `match.route.loader`/`match.route.action` directly - but we have since decided against that with the `match.handler` API.
59
+
We also originally intended to expose a `type: 'loader' | 'action`' field as a way to presumably let them call `match.route.loader`/`match.route.action` directly - but we have since decided against that with the `match.resolve` API.
60
60
61
-
⚠️ `type` was eliminated in favor of `match.handler`.
61
+
⚠️ `type` was eliminated in favor of `match.resolve`.
62
62
63
63
`dataStrategy` is control _when_ handlers are called, not _how_. RR is in charge of calling them with the right parameters.
64
64
@@ -89,7 +89,7 @@ Initially, we intended for `dataStrategy` to handle (3), and considered an optio
89
89
### Handling `route.lazy`
90
90
91
91
There's a nuanced step we missed in our sequential steps above. If a route was
92
-
using `route.lazy`, we may need to load the rout before we can execute the `loader`. There's two options here:
92
+
using `route.lazy`, we may need to load the route before we can execute the `loader`. There's two options here:
93
93
94
94
1. We pre-execute all `route.lazy` methods before calling `dataStrategy`
95
95
2. We let `dataStrategy` execute them accordingly
@@ -129,7 +129,7 @@ function dataStrategy({ matches, defaultStrategy }) {
129
129
}
130
130
```
131
131
132
-
⚠️ We are actively seeing if we can eliminate this via `match.handler`
132
+
⚠️ This match.route as a function API was removed in favor of `match.resolve`
133
133
134
134
### Handling `shouldRevalidate` behavior
135
135
@@ -326,33 +326,94 @@ Extending on the idea above - it all started to feel super leaky and full of imp
326
326
327
327
That's wayyyy to many rough edges for us to document and users to get wrong (rightfully so!).
328
328
329
-
Why can't we just do it all? LE'ts wrap _all_ of that up into a single `match.handler()` function that:
329
+
Why can't we just do it all? Let's wrap _all_ of that up into a single `match.resolve()` function that:
330
330
331
331
- Waits for `route.lazy` to resolve (if needed)
332
332
- No-ops if the route isn't supposed to revalidate
333
333
- Open question here if we return the _current_ data from these no-ops or return `undefined`?
334
+
- We decided _not_ to expose this data for now since we don't have a good use case
334
335
- Knows whether to call the `loader` or the `action`
335
336
- Allows users to pass _additional_ params to loaders/actions for middleware/context use cases.
336
337
337
338
```js
339
+
// Simplest case - call all loaders in parallel just like current behavior
338
340
functiondataStrategy({ matches }) {
339
341
// No more type, defaultStrategy, or match.route promise APIs!
342
+
returnPromise.all(matches.map(match=> {
343
+
// resolve `route.lazy` if needed and call loader/action
344
+
returnm.resolve();
345
+
});
346
+
}
340
347
348
+
// More advanced case - call loader sequentially passing a context through
349
+
asyncfunctiondataStrategy({ matches }) {
350
+
let ctx = {};
351
+
let results = [];
352
+
for (let match of matches) {
353
+
// You can pass a "handlerOverride" function to resolve giving you control
354
+
// over how/if to call the handler. The argument passed to `handler` will
355
+
// be passed as the second argument to your `loader`/`action`:
356
+
// function loader({ request }, ctx) {...}
357
+
let result =awaitm.resolve((handler) => {
358
+
returnhandler(ctx);
359
+
});
360
+
results.push(result);
361
+
});
362
+
return results;
363
+
}
364
+
365
+
// More performant case leveraging a middleware type abstraction which lets loaders
366
+
// still run in parallel after sequential middlewares:
367
+
functiondataStrategy({ matches }) {
341
368
// Can implement middleware as above since you now get all matches
342
369
let context =runMiddlewares(matches);
343
370
344
371
// Call all loaders in parallel (no params to pass) but you _can_ pass you
345
-
// own argument to `handler` and it will come in as `loader({ request }, handlerArg)`
372
+
// own argument to `resolve` and it will come in as `loader({ request }, handlerArg)`
346
373
// So you can send middleware context through to loaders/actions
// Don't even call the handler since we have the data we need from single fetch
392
+
let result =awaitm.resolve(() => {
393
+
if (singleFetchData.errors?.[m.route.id]) {
394
+
return {
395
+
type:'error',
396
+
result:singleFetchData.errors?.[m.route.id]
397
+
}
398
+
}
399
+
return {
400
+
type:'data',
401
+
result:singleFetchData.data?.[m.route.id]
402
+
}
403
+
});
404
+
results.push(result);
405
+
});
406
+
return results;
407
+
}
353
408
```
354
409
355
-
## Consequences
410
+
## Status codes
411
+
412
+
Initially, we thought we could just let the `handlerOverride`return or throw and then internally we could convert the returned/thrown valuer into a `HandlerResult`. However, this didn't work for the `unstable_skipActionRevalidation` behavior we wanted to implement with Single Fetch.
413
+
414
+
If users returned normal Response's it would be fine, since we could decode the response internally and also know the status. However, if user's wanted to do custom response decoding (i.e., use `turbo-stream` like we did in single fetch) then there was no way to return/throw data _and the status code from the response_ without introducing something like the `ErrorResponse` API which holds a status and data. We decided to make `HandlerResult` public API and put an optional `status` field on it.
415
+
416
+
This means that if you just call resolve with no `handlerOverride` you never need to know about `HandlerResult`. If you do pass a `handlerOverride`, then you need to return a proper HandlerResult with `type:"data"|"error"`.
0 commit comments