Closed frontsideair closed 10 months ago
Yes, I haven't had a chance to add support for defer
, but it is on my TODO list.
I'm just here to say that i'm waiting eagerly for the typedDefer update haha
Thanks for create this awesome library. I hope this wil be well maintained.
Working on this now. Almost there.
Hey, any update on this?
The more I work on this, the more I realize it may not be possible without help from Remix.
I can get the "fast data" transferred just fine since I know the values and the "types" at call time. However, I don't have the actual data for "deferred data," just a Promise, so I cannot serialize and send the metadata.
The issue is that Remix doesn't provide any hooks into the deferred response pipeline, so I can't use the typedjson serializer on the Promise result.
I could create a patch, but that's not a long-term solution. Ideally, Remix would provide hooks for an encode/decode function (default being JSON.stringify/parse
).
I'll keep you all posted.
function enqueueTrackedPromise(
controller: any,
encoder: TextEncoder,
settledKey: string,
promise: TrackedPromise
) {
if ("_error" in promise) {
controller.enqueue(
encoder.encode(
"error:" +
JSON.stringify({
[settledKey]: serializeError(promise._error),
}) +
"\n\n"
)
);
} else {
controller.enqueue(
encoder.encode(
"data:" +
JSON.stringify({ [settledKey]: promise._data ?? null }) +
"\n\n"
)
);
}
}
That's rough... It might pay to push for an update to the system anyway, they've mentioned it's not where they want it right now?
It wouldn't be a breaking change for them to add the hook, so I don't think they'd reject if you had the explanation and a PR?
Yeah, I'm going to play with adding that feature. At least show there's a need and that it wouldn't be intrusive.
I added this to entry.server just to see what would happen and sure enough all calls to JSON.stringify/parse
got rerouted.
// monkey patch JSON.stringify and JSON.parse
let oldStringify = JSON.stringify;
let oldParse = JSON.parse;
JSON.stringify = function (
value: any,
replacer?: (
this: any,
key: string,
value: any
) => any | (number | string)[] | null,
space?: string | number
) {
console.log("stringify", value);
return oldStringify(value, replacer, space);
};
JSON.parse = function (
text: string,
reviver?: (this: any, key: string, value: any) => any
) {
console.log("parse", text);
return oldParse(text, reviver);
};
Anyway, I may be able to handle this entirely in userland.
That is filthy but I love it haha
Making progress. Deferred data is now encoded via typedjson
. Unfortunately, it does require a patch. Hopefully, we'll get something like this added to the core.
In entry.server, you add the following line. This replaces the standard JSON calls.
setDataTransformer({
encode: typedjson.stringifyRemix,
decode: typedjson.deserializeRemix,
});
I'll give it a go today in my project and let you know if I find any issues 👍
Sorry if I wasn't clear, but this is still work in progress. I hope to have a release by the end of the week. Thanks!
Sorry if I wasn't clear, but this is still work in progress. I hope to have a release by the end of the week. Thanks!
I was thinking that when I sent the message and saw there was no release after haha All good!
@kiliman Couldn't you do something like this:
// serialize responses from promises on the server as soon as they resolve
const typedDefer: typeof defer = (data, ...args) => {
Object.entries(defer).forEach(([key, value]) => {
if (value instanceof Promise) {
Object.assign(data, {
[key]: value.then((resolvedData) => serialize(resolvedData)),
});
}
else {
Object.assign(data, {
[key]: serialize(value),
});
}
});
return defer(data, ...args);
};
// deserialize responses from promises on the client as soon as they resolve
const TypedAwait = (props) => {
return <Await {...props}>
{(resolvedData) => props.children(deserialize(resolvedData))}
</Await>
}
const loader = () => {
//...
const myPromise = fetch(/* ... */);
return typedDefer({ myPromise });
}
export default function Component() {
const { myPromise } = useTypedDefer<typeof loader>();
return <Suspense fallback={<p>Loading..</p>}>
<TypedAwait
resolve={myPromise }
errorElement={<p>Error!</p>}>
{(response) => {
return <p>hi</p>;
}}
</TypedAwait>
</Suspense>
}
This works at least on my end.. useTypedDefer
is basically a wrapper around useLoaderData
which knows whether a field is a promise or not.
Any updates on this? Would be amazing to have some news @kiliman! Thanks for the work you have set into this!
Thank you so much @kiliman! Been waiting for this one! 🎉🙏
BTW: I just want to thank @KingSora for posting his snippet. It was the basis for the actual working version. 🙌
If you're using typeddefer
in your project, let me know how it's working out for you. I hope I covered all the edge-cases.
Now that Remix is React-Router'ed, I think a wrapper around
defer
is needed. AuseTypedAsyncValue
counterpart may be needed as well.