Skip to content

Commit

Permalink
defer - for real this time (#4920)
Browse files Browse the repository at this point in the history
feat: lazy data via `defer()` and `<Await>`
chore: bump @remix-run/router to 1.3.0-pre.2
chore: bump react-router-dom to 6.7.0-pre.3
  • Loading branch information
jacob-ebey committed Jan 13, 2023
1 parent efe3e28 commit be232a0
Show file tree
Hide file tree
Showing 27 changed files with 2,312 additions and 49 deletions.
22 changes: 22 additions & 0 deletions .changeset/old-cameras-lay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
"remix": patch
"@remix-run/cloudflare": patch
"@remix-run/deno": patch
"@remix-run/node": patch
"@remix-run/react": patch
"@remix-run/serve": patch
"@remix-run/server-runtime": patch
"@remix-run/testing": patch
---

Introduces the `defer()` API from `@remix-run/router` with support for server-rendering and HTTP streaming. This utility allows you to defer values returned from loaders by passing promises instead of resolved values. This has been refered to as "promise over the wire".

Informational Resources:
- https://gist.github.com/jacob-ebey/9bde9546c1aafaa6bc8c242054b1be26
- https://github.com/remix-run/remix/blob/main/decisions/0004-streaming-apis.md

Documentation Resources (better docs specific to remix are in the works):
- https://reactrouter.com/en/main/utils/defer
- https://reactrouter.com/en/main/components/await
- https://reactrouter.com/en/main/hooks/use-async-value
- https://reactrouter.com/en/main/hooks/use-async-error
93 changes: 93 additions & 0 deletions integration/defer-loader-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { test, expect } from "@playwright/test";

import { createAppFixture, createFixture, js } from "./helpers/create-fixture";
import type { Fixture, AppFixture } from "./helpers/create-fixture";
import { PlaywrightFixture } from "./helpers/playwright-fixture";

let fixture: Fixture;
let appFixture: AppFixture;

test.beforeAll(async () => {
fixture = await createFixture({
files: {
"app/routes/index.jsx": js`
import { useLoaderData, Link } from "@remix-run/react";
export default function Index() {
return (
<div>
<Link to="/redirect">Redirect</Link>
<Link to="/direct-promise-access">Direct Promise Access</Link>
</div>
)
}
`,

"app/routes/redirect.jsx": js`
import { defer } from "@remix-run/node";
export function loader() {
return defer({food: "pizza"}, { status: 301, headers: { Location: "/?redirected" } });
}
export default function Redirect() {return null;}
`,

"app/routes/direct-promise-access.jsx": js`
import * as React from "react";
import { defer } from "@remix-run/node";
import { useLoaderData, Link, Await } from "@remix-run/react";
export function loader() {
return defer({
bar: new Promise(async (resolve, reject) => {
resolve("hamburger");
}),
});
}
let count = 0;
export default function Index() {
let {bar} = useLoaderData();
React.useEffect(() => {
let aborted = false;
bar.then((data) => {
if (aborted) return;
document.getElementById("content").innerHTML = data + " " + (++count);
document.getElementById("content").setAttribute("data-done", "");
});
return () => {
aborted = true;
};
}, [bar]);
return (
<div id="content">
Waiting for client hydration....
</div>
)
}
`,
},
});

appFixture = await createAppFixture(fixture);
});

test.afterAll(async () => appFixture.close());

test("deferred response can redirect on document request", async ({ page }) => {
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/redirect");
await page.waitForURL(/\?redirected/);
});

test("deferred response can redirect on transition", async ({ page }) => {
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/");
await app.clickLink("/redirect");
await page.waitForURL(/\?redirected/);
});

test("can directly access result from deferred promise on document request", async ({
page,
}) => {
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/direct-promise-access");
let element = await page.waitForSelector("[data-done]");
expect(await element.innerText()).toMatch("hamburger 1");
});
Loading

0 comments on commit be232a0

Please sign in to comment.