Open JClackett opened 1 year ago
Was just about to start an issue on this, but saw that @JClackett already did.
I created a demo that replicates the error: https://github.com/xHomu/remix-defer-indie-stack/blob/main/app/routes/index.tsx
I have a similar issue and the same error message. It's working great when I navigate to the page were the defer is used but I receive the same error message in the browser console when I reload the page.
I tried to reproduce the bug with a basic example but it's working as expected. I think it's something that is coming from my stack, maybe i18n...
I don't use prisma or i18n but have the same issue. Even when deferring something like this for testing:
export async function getCurrentUser() {
await new Promise((r) => setTimeout(r, 2000));
return {
email: "example@example.com",
first_name: "First Name",
last_name: "Last Name",
};
}
So I just had the same problem come up and if anyone is interested, I've been building out an ensuredPromise
for any time I want to defer something... If wrapping in a promise solves the issue you can just do it once and not have to worry again:
export async function ensuredPromise<T, P extends string | number | null>(promiseFunction: (prop: NonNullable<P>) => Promise<T>, prop: P) {
return !!prop
? new Promise(async resolve => {
const res = await promiseFunction(prop);
return resolve(res);
})
: (async () => null)();
}
It's messy, but i've only needed to pass a single string or number through because I make helper functions, it's also handling the scenario where you pass through a potentially undefined prop, defer always needs a promise that resolves null (undefined will break it)
Any updates on this? I have to wrap every prisma call in a new Promise
which also loses the type safety
EDIT: Referring to this part
Error: This Suspense boundary received an update before it finished hydrating. This caused the boundary to switch to client rendering. The usual way to fix this is to wrap the original update in startTransition.
FWIW, I'm using 1.15
with defer
, and wrapping my prisma queries in a promise doesn't resolve / workaround the issue for me. On initial load, the deferred data will never resolve
I think this issue extends beyond prisma too and is also being tracked by https://github.com/remix-run/remix/issues/5760
In the meanwhile, you can use either of the following:
// .then(result => result)
return defer({
users: db.user.findMany({ ... }).then(u => u)
});
return defer({
users: Promise.resolve().then(() => db.user.findMany({ ... })
});
Both of these should retain type information!
This likely happens because Prisma doesn't actually return a Promise until you call .then.
You could build a small wrapper for it that lets you use defer
as usual:
// I have NOT tested this
import { defer as remixDefer } from "@remix-run/node";
export function defer<Data extends Record<string, unknown>>(data: Data) {
return Object.fromEntries(
Object.entries()(
([key, value]) => [key, "then" in value ? value.then(r => r) : value]
)
);
}
// in your loader, use as normal:
return defer({
users: db.users.findMany({ ... })
});
Just want to inform the problem still exists and it is not related to prisma. It's related to <Await>
component and it's conflict with React's <Suspense>
component.
@mikkpokk Can You provide some more details or a link to a discussion about this problem? We do have the very same problem. The problem appears when a page is server side rendered and the page's loader returns deferred data. This issue appeared also in @kentcdodds "Advanced Remix" course in Frontend Masters.
@gforro Unfortunately, I didn't had chance to dig deeper and I wrote my own hook instead to resolve the issue in my project.
Usage inside component:
...
const [deferred_data, deferred_loading, deferred_error] = useResolveDeferredData(data?.deferred_data)
...
Hook itself:
const isPromise = (input) => input && typeof input.then === 'function'
const useResolveDeferredData = (input, emptyDataState = {}) => {
const [data, setData] = useState<any>(isPromise(input) ? emptyDataState : input)
const [loading, setLoading] = useState<boolean>(isPromise(input))
const [error, setError] = useState<string|null>(null)
useEffect(() => {
if (isPromise(input)) {
setLoading(true)
Promise.resolve(input).then(data => {
setData(data)
setLoading(false)
}).catch((error) => {
if (error.message !== 'Deferred data aborted') {
// This should fire only in case of unexpected or expected server error
setData(emptyDataState)
setError(error.message)
setLoading(false)
}
})
} else {
setData(input)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [input])
return [
data,
loading,
error,
]
}
export default useResolveDeferredData
I also encountered this issue.
In my case the Prisma call was in a function. Marking the function async
solved it.
I think it's a fair workaround for now...
It’s Prisma who needs to abide to a standard Promise format. This is not a remix issue
I also have this issue.
It’s Prisma who needs to abide to a standard Promise format. This is not a remix issue.
I think it's not prisma related. A few people in this thread including myself have this issue without using prisma.
It’s Prisma who needs to abide to a standard Promise format. This is not a remix issue.
I think it's not prisma related. A few people in this thread including myself have this issue without using prisma.
may I have a non-prisma example? this thread is about prisma
we are facing the same issue, with mock data as described by @adanielyan
Is there any update on this regarding non-prisma calls?
If you're getting hydration issues on the initial page load, this will break defer
and streaming results. This is because React re-renders your app after a hydration mismatch, so Remix no longer handles the streamed results. This affects any deferred promises, not just those from Prisma.
This is a known issue with how React 18.2 handles the hydration of the entire document vs a single div. This is primarily due to browser extensions that mutate the DOM before hydration.
The current solution is to use React Canary (currently the pre-release of v19).
Please take a look at my example. This uses the new Single Data Fetch feature (v2.9), enabling you to return promises directly (including nested promises) without using defer
. It also shows how to use React Canary and overrides
.
@kiliman Hey, no, I don't have any hydration issues. What breaks it is simply using await sleep(1000)
in an async function that returns DB query (no prisma)
@jansedlon Do you have an example repo that I can check out?
@kiliman Uhhh, I'll try to make one
I think the issue is similar to https://github.com/remix-run/remix/issues/9440, remix judges whether the deferred data instance is Promise based on instanceof Promise, but some data may wrap Promise, resulting in the destruction of the subsequent process
That's also why we wrap it up with Promise and it solves the problem
What version of Remix are you using?
1.11.0
Steps to Reproduce
Using the new defer function with a prisma call breaks the Await component.
When logging the render prop value its just an empty object i.e
{}
After a bit of debugging I noticed that the promise return type of a prisma call is a
PrismaPromise
, it seems the Await component doesn't like this.Wrapping the prisma call in Promise seems to solve the issue:
Even after the fix above, I also get this error in the browser console
Error: This Suspense boundary received an update before it finished hydrating. This caused the boundary to switch to client rendering. The usual way to fix this is to wrap the original update in startTransition.
Expected Behavior
Defers loading prisma call
Actual Behavior
Blows up