Open ramiroazar opened 1 week ago
I think the problem is action
does not support defer
.
Check react-router
sources here:
loader
handles DeferedResult
:
https://github.com/remix-run/react-router/blob/58d8f316e0c682efaaa012125ff152ffa9b36ec8/packages/router/router.ts#L5267
action
throws an error when it is `DeferedResult``:
https://github.com/remix-run/react-router/blob/58d8f316e0c682efaaa012125ff152ffa9b36ec8/packages/router/router.ts#L1796-L1798
However, handleAction
does not throw an error as expected.
After debugging, I found the problem here: https://github.com/remix-run/react-router/blob/58d8f316e0c682efaaa012125ff152ffa9b36ec8/packages/router/router.ts#L4905
let handlerPromise: Promise<DataStrategyResult> = (async () => {
try {
let val = await (handlerOverride
? handlerOverride((ctx: unknown) => actualHandler(ctx))
: actualHandler());
return { type: "data", result: val };
} catch (e) {
return { type: "error", result: e };
}
})();
return Promise.race([handlerPromise, abortPromise]);
The val
that the above logic return is a Response
, which will be treated as normal data
result in convertDataStrategyResultToDataResult
:
https://github.com/remix-run/react-router/blob/58d8f316e0c682efaaa012125ff152ffa9b36ec8/packages/router/router.ts#L5023-L5028
On the contrary, loader return a DefferedResult
at the same place, which is the key difference:
// val of loader is a DefferedResult other than a Response
let val = await (handlerOverride
? handlerOverride((ctx: unknown) => actualHandler(ctx))
: actualHandler());
The difference is caused by actualHandler
.
For loader it is: https://github.com/remix-run/remix/blob/5954ad1520e921483fbe07f5995c6f9d92e7a37b/packages/remix-react/routes.tsx#L333
For action it is: https://github.com/remix-run/remix/blob/5954ad1520e921483fbe07f5995c6f9d92e7a37b/packages/remix-react/routes.tsx#L383
In conclusion, the action does not support defer
like the loader. And dataRoute.action
implemented in remix
always return a response, which makes the expected DeferredResult
error will never be thrown in react-route
.
Now that react-router
doesn't like defer
in action, I think we should update the docs of defer
and throw an error in remix
when people try to do so.
@laishere is correct in that actions do not support defer
(and never have). It looks like somewhere along the way we lost the error message that used to be thrown when you tried that.
It's worth noting that with Single Fetch this limitation is removed and you can return promises from actions in plain javascript objects, no need for defer
.
@brophdawg11 I believe it's because the action handler return a Response
other than actual data, so it will always be treated as DataResult
not DeferredResult
in react-router
here, which will bypass DeferredResult
type check and no error will be thrown.
Action handler defined in remix
:
https://github.com/remix-run/remix/blob/5954ad1520e921483fbe07f5995c6f9d92e7a37b/packages/remix-react/routes.tsx#L383
Reproduction
/test
routetext
inputI originally encountered this using the Cloudflare template with
@remix-run/cloudflare
, but I replicated this in the reproduction using@remix-run/node
.System Info
Used Package Manager
npm
Expected Behavior
The
useActionData
hook should just return the serialised data from the most recent route action.Actual Behavior
The useActionData hook does return the correct data, but within another
data
property as part of an object with other properties such asabortPromise
,deferredKeys
,pendingKeysSet
, etc.This doesn't match the types from
useActionData<typeof action>()
, so trying to access thedata
property throws the following type error.Property 'data' does not exist on type '({} & { text: string | JsonifyObject<File> | null; } & {}) | undefined'.
The only related issue I could find is https://github.com/remix-run/blues-stack/issues/151.