remix icon indicating copy to clipboard operation
remix copied to clipboard

feat: the real implementation of deferred

Open jacob-ebey opened this issue 2 years ago • 11 comments

🚨 IMPORTANT: Revert any changes to version and release scripts before merging 🚨


jacob-ebey avatar Jun 10 '22 03:06 jacob-ebey

🦋 Changeset detected

Latest commit: cbec20ee92cad1bb0e2eebff8bbf3c20454dd2c8

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 17 packages
Name Type
@remix-run/cloudflare Major
@remix-run/deferred Major
@remix-run/deno Major
@remix-run/react Major
@remix-run/server-runtime Major
@remix-run/cloudflare-pages Major
@remix-run/cloudflare-workers Major
@remix-run/dev Major
@remix-run/node Major
create-remix Major
@remix-run/architect Major
@remix-run/express Major
@remix-run/netlify Major
@remix-run/vercel Major
@remix-run/serve Major
remix Major
@remix-run/eslint-config Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

changeset-bot[bot] avatar Jul 08 '22 15:07 changeset-bot[bot]

deferred-benefits

jacob-ebey avatar Jul 18 '22 20:07 jacob-ebey

Could you double-check the highlighted lines?

Just went through them and they look good to me.

jacob-ebey avatar Jul 19 '22 21:07 jacob-ebey

can anyone share the status of the deferred API? when will this PR be merged ?

lili21 avatar Oct 25 '22 07:10 lili21

It will be part of "Remixing React Router" where they port Remix on top of React Router 6.4 (which was just released). They are working on the port now. I'm not sure of the timeline. If you want to try out the features, you can install the remix packages using the deferred version tag. image

https://remix.run/blog/remixing-react-router

kiliman avatar Oct 25 '22 20:10 kiliman

How to access the error reason if the defer value rejected, like inside the errorElement? is there a useAsyncError hook?

lili21 avatar Oct 31 '22 07:10 lili21

@lili21 Yep! I would refer to the React Router docs for now as the eventual stable Remix API should match: https://reactrouter.com/en/main/guides/deferred and https://reactrouter.com/en/main/components/await

brophdawg11 avatar Oct 31 '22 19:10 brophdawg11

Heya 👋

I found a TypeScript bug. In a situation where you want to A/B test deferring some data, the render prop doesn't seem to know what to do with the type. Here's my loader:

export async function loader({ request, params }: LoaderArgs) {
  const user = await requireUser(request);
  const { customerId } = params;
  invariant(customerId, "customerId param is required");
  const customerInfo = await getCustomerInfo(customerId);
  if (!customerInfo) {
    throw new Response("not found", { status: 404 });
  }
  const invoiceDetailsPromise = getCustomerInvoiceDetails(customerId);
  const deferInvoices = await shouldDeferInvoices({
    customerId,
    userId: user.id,
  });
  return defer({
    customerInfo,
    invoiceDetails: deferInvoices
      ? invoiceDetailsPromise
      : await invoiceDetailsPromise,
  });
}

And here's the Await usage:

<Suspense fallback={<InvoiceDetailsFallback />}>
  <Await
    resolve={data.invoiceDetails}
    errorElement={
      <div className="relative h-full">
        <ErrorFallback />
      </div>
    }
  >
    {(invoiceDetails) => (
      <table className="w-full">
        <tbody>
          {/* @ts-expect-error This needs to be fixed in Remix */}
          {invoiceDetails.map((details) => (
            <Etc />
          ))}
        </tbody>
      </table>
    )}
  </Await>
</Suspense>

The TypeScript error I get on invoiceDetails is:

Property 'map' does not exist on type 'SerializeObject<{ totalAmount: number; totalDeposits: number; daysToDueDate: number; dueStatus: DueStatus; dueStatusDisplay: string; id: string; number: number; }>[] | SerializeObject<...>'.
  Property 'map' does not exist on type 'SerializeObject<{ then: <TResult1 = { totalAmount: number; totalDeposits: number; daysToDueDate: number; dueStatus: DueStatus; dueStatusDisplay: string; id: string; number: number; }[], TResult2 = never>(onfulfilled?: ((value: { totalAmount: number; ... 5 more ...; number: number; }[]) => TResult1 | PromiseLike<...>...'.

Interestingly, if I change my loader for invoiceDetails to either only await or only not await, then it works fine. It's just not jazzed about using both.

Here's the spot in my repo: https://github.com/kentcdodds/stream-away-the-wait-talk/blob/2526cafd0a758c20c3ef70ba78b008bacabd19bc/apps/11-levers/app/routes/__app/sales/customers/%24customerId.tsx#L70

Feel free to reproduce via:

npx create-remix ./defer-types-issue-repro --template=https://github.com/kentcdodds/stream-away-the-wait-talk/tree/main/apps/11-levers --typescript --install
cd ./defer-types-issue-repro
open "app/routes/__app/sales/customers/\$customerId.tsx"
# delete the ts-expect-error on line 70
npm run typecheck

kentcdodds avatar Nov 01 '22 00:11 kentcdodds

does it work inside worker runtime, like cloudflare worker?

lili21 avatar Nov 03 '22 06:11 lili21

Reporting an error I'm seeing with a page using defer to stream my page. Stackblitz reproducing the error (same behavior happens on my Mac):

  • https://stackblitz.com/edit/node-whsnyv

I am using the deferred tag & package.json looks like this:

{
  "private": true,
  "sideEffects": false,
  "scripts": {
    "build": "remix build",
    "dev": "remix dev",
    "start": "remix-serve build"
  },
  "dependencies": {
    "@remix-run/node": "deferred",
    "@remix-run/react": "deferred",
    "@remix-run/serve": "deferred",
    "isbot": "^3.5.4",
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "devDependencies": {
    "@remix-run/dev": "deferred",
    "@remix-run/eslint-config": "deferred",
    "@types/react": "^18.0.15",
    "@types/react-dom": "^18.0.6",
    "eslint": "^8.23.1",
    "typescript": "^4.7.4"
  },
  "engines": {
    "node": ">=14"
  }
}

Steps to Reproduce

  1. Load route which is using defer
  2. Refresh the route which is using defer and you'll see the 2nd time it no longer works (see terminal error) and the page hangs Stackblitz reproducing the error: https://stackblitz.com/edit/node-whsnyv
Error: The render was aborted by the server without a reason.
    at abortTask (/Users/cfajardo/Repos/remix-ssr-vs-ssr-streaming/node_modules/react-dom/cjs/react-dom-server.node.development.js:6436:43)
    at /Users/cfajardo/Repos/remix-ssr-vs-ssr-streaming/node_modules/react-dom/cjs/react-dom-server.node.development.js:7002:14
    at Set.forEach (<anonymous>)
    at abort (/Users/cfajardo/Repos/remix-ssr-vs-ssr-streaming/node_modules/react-dom/cjs/react-dom-server.node.development.js:7001:20)
    at Timeout.abort [as _onTimeout] (/Users/cfajardo/Repos/remix-ssr-vs-ssr-streaming/node_modules/react-dom/cjs/react-dom-server.node.development.js:7051:7)
    at listOnTimeout (node:internal/timers:559:17)
    at processTimers (node:internal/timers:502:7)

Video Reproduction:

https://user-images.githubusercontent.com/6743796/200146863-71ac5799-eb70-44c2-8137-5ff6f4db2d77.mp4

cliffordfajardo avatar Nov 05 '22 23:11 cliffordfajardo

does it work inside worker runtime, like cloudflare worker?

✘ [ERROR] Could not resolve "fs/promises"

when I'm trying to use defer in the cloudflare worker template.

lili21 avatar Nov 09 '22 08:11 lili21

@cliffordfajardo try decrease the getPeopleDelayTime value. or increase the ABORT_DELAY value inside entry.server.tsx.

lili21 avatar Nov 21 '22 07:11 lili21

if the deferred promise reject, it will throw the unhandlePromiseRejection error, which will break the application by default.

lili21 avatar Dec 13 '22 11:12 lili21

It was a good run 😅

For anyone looking for the new PR: https://github.com/remix-run/react-router/pull/9760

kentcdodds avatar Dec 22 '22 14:12 kentcdodds