Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion integration-test/playwright/src/cc-dynamic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,29 @@ test.describe("CC dynamic", () => {
expect(await page.getByText("loading...").count()).toBe(6);
// deferred chunk came in
await expect(page.getByText("cuteness overload")).toBeVisible();
expect(await page.getByText("Queried in SSR environment").count()).toBe(7);
expect(await page.getByText("loading...").count()).toBe(0);
});

test("client loader", { tag: ["@react-router"] }, async ({ page }) => {
await page.goto(`${base}/clientLoader`, {
waitUntil: "commit",
});

// main data already there
await expect(page.getByText("Soft Warm Apollo Beanie")).toBeVisible();
expect(await page.getByText("Queried in browser environment").count()).toBe(
1
);
// deferred chunks still loading
expect(await page.getByText("loading...").count()).toBe(6);
// deferred chunk came in
await expect(page.getByText("cuteness overload")).toBeVisible();
await new Promise((resolve) => setTimeout(resolve, 500));

expect(await page.getByText("Queried in SSR environment").count()).toBe(7);
expect(await page.getByText("Queried in browser environment").count()).toBe(
7
);
expect(await page.getByText("loading...").count()).toBe(0);
});
});
1 change: 1 addition & 0 deletions integration-test/react-router/app/apollo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ export const makeClient = (request?: Request) => {
link,
});
};

export const apolloLoader = createApolloLoaderHandler(makeClient);
11 changes: 10 additions & 1 deletion integration-test/react-router/app/entry.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,23 @@ import { hydrateRoot } from "react-dom/client";
import { HydratedRouter } from "react-router/dom";
import { makeClient } from "./apollo";
import { ApolloProvider } from "@apollo/client/react/index.js";
import { initializeApolloContext } from "@apollo/client-integration-react-router";

startTransition(() => {
const client = makeClient();
hydrateRoot(
document,
<StrictMode>
<ApolloProvider client={client}>
<HydratedRouter />
<HydratedRouter
unstable_getContext={() => {
const context = new Map();
// set other context values here
return initializeApolloContext(client, context);
}}
/>
{/* if you have no other context values, as a shortcut */}
{/* <HydratedRouter unstable_getContext={() => initializeApolloContext(client)} /> */}
</ApolloProvider>
</StrictMode>
);
Expand Down
74 changes: 74 additions & 0 deletions integration-test/react-router/app/routes/clientLoader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import type { Route } from "./+types/clientLoader";
import {
useApolloClient,
useQueryRefHandlers,
useReadQuery,
} from "@apollo/client/react/index.js";
import {} from "~/apollo";
import { DEFERRED_QUERY } from "@integration-test/shared/queries";
import { useTransition } from "react";
import { apolloContext } from "@apollo/client-integration-react-router";

export async function clientLoader({ context }: Route.LoaderArgs) {
const { preloadQuery } = context.get(apolloContext);
const queryRef = preloadQuery(DEFERRED_QUERY, {
variables: { delayDeferred: 1000 },
});

await new Promise((resolve) => setTimeout(resolve, 300));

return {
queryRef,
};
}

export default function WithClientLoader({ loaderData }: Route.ComponentProps) {
const { queryRef } = loaderData;

const { refetch } = useQueryRefHandlers(queryRef);
const [refetching, startTransition] = useTransition();
const { data } = useReadQuery(queryRef);
const client = useApolloClient();

return (
<>
<ul>
{data.products.map(({ id, title, rating }) => (
<li key={id}>
{title}
<br />
Rating:{" "}
<div style={{ display: "inline-block", verticalAlign: "text-top" }}>
{rating?.value || ""}
<br />
{rating ? `Queried in ${rating.env} environment` : "loading..."}
</div>
</li>
))}
</ul>
<p>Queried in {data.env} environment</p>
<button
disabled={refetching}
onClick={() => {
client.cache.batch({
update(cache) {
for (const product of data.products) {
cache.modify({
id: cache.identify(product),
fields: {
rating: () => null,
},
});
}
},
});
startTransition(() => {
refetch();
});
}}
>
{refetching ? "refetching..." : "refetch"}
</button>
</>
);
}
10 changes: 5 additions & 5 deletions integration-test/react-router/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,18 @@
"@apollo/client": "^3.13.4",
"@apollo/client-integration-react-router": "workspace:^",
"@integration-test/shared": "workspace:^",
"@react-router/fs-routes": "^7.2.0-pre.3",
"@react-router/node": "^7.2.0-pre.3",
"@react-router/serve": "^7.2.0-pre.3",
"@react-router/fs-routes": "^7.3.0",
"@react-router/node": "^7.3.0",
"@react-router/serve": "^7.3.0",
"@vercel/react-router": "^1.0.2",
"graphql": "*",
"isbot": "^5.1.17",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-router": "^7.2.0-pre.3"
"react-router": "^7.3.0"
},
"devDependencies": {
"@react-router/dev": "^7.2.0-pre.3",
"@react-router/dev": "^7.3.0",
"@tailwindcss/vite": "^4.0.0",
"@types/node": "^20",
"@types/react": "^19.0.1",
Expand Down
10 changes: 10 additions & 0 deletions integration-test/react-router/react-router.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,19 @@ if (process.env.VERCEL) {
presets.push(vercelPreset());
}

declare module "react-router" {
interface Future {
unstable_middleware: true; // 👈 Enable middleware types
}
}

export default {
// Config options...
// Server-side render by default, to enable SPA mode set this to `false`
ssr: true,
presets,
future: {
// https://reactrouter.com/start/changelog#middleware-unstable
unstable_middleware: true,
},
} satisfies Config;
4 changes: 2 additions & 2 deletions packages/react-router/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
"publint": "0.2.7",
"react": "^19.0.0",
"react-dom": "*",
"react-router": "^7.2.0-pre.3",
"react-router": "^7.3.0",
"rimraf": "5.0.5",
"tsup": "8.0.2",
"typescript": "5.4.5",
Expand All @@ -88,7 +88,7 @@
"peerDependencies": {
"@apollo/client": "^3.13.0",
"react": "^19",
"react-router": "^7.2.0-pre.3"
"react-router": "^7.3.0"
},
"dependencies": {
"@apollo/client-react-streaming": "0.12.0-alpha.3",
Expand Down
6 changes: 5 additions & 1 deletion packages/react-router/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
export { ApolloClient } from "./ApolloClient.js";
export { ApolloHydrationHelper } from "./ApolloHydrationHelper.js";
export { createApolloLoaderHandler } from "./preloader.js";
export {
apolloContext,
createApolloLoaderHandler,
initializeApolloContext,
} from "./preloader.js";
35 changes: 29 additions & 6 deletions packages/react-router/src/preloader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,31 @@ import {
} from "@apollo/client-react-streaming";
import type { Promiscade } from "promiscade";
import { promiscadeToReadableStream, streamToPromiscade } from "promiscade";
import type { unstable_SerializesTo } from "react-router";
import {
unstable_createContext,
type unstable_InitialContext,
type unstable_SerializesTo,
} from "react-router";
import type { JsonString } from "@apollo/client-react-streaming/stream-utils";

import {
createQueryPreloader,
type PreloadQueryFunction,
} from "@apollo/client/react/index.js";

type MarkedForSerialization<T> =
T extends TransportedQueryRef<infer Data, infer Variables>
? unstable_SerializesTo<QueryRef<Data, Variables>>
: { [K in keyof T]: MarkedForSerialization<T[K]> };

export type ApolloContext = {
preloadQuery: PreloadTransportedQueryFunction;
};

type ApolloLoader = <LoaderArgs extends CreateServerLoaderArgs<any>>() => <
ReturnValue,
>(
loader: (
args: LoaderArgs & {
preloadQuery: PreloadTransportedQueryFunction;
}
) => ReturnValue
loader: (args: LoaderArgs & ApolloContext) => ReturnValue
) => (args: LoaderArgs) => MarkedForSerialization<ReturnValue>;

export function createApolloLoaderHandler(
Expand All @@ -47,6 +56,20 @@ export function createApolloLoaderHandler(
};
}

export const apolloContext = unstable_createContext<{
client: ApolloClient;
preloadQuery: PreloadQueryFunction;
}>();

export function initializeApolloContext(
client: ApolloClient,
contextMap: unstable_InitialContext = new Map()
) {
const preloader = createQueryPreloader(client);
contextMap.set(apolloContext, { client, preloadQuery: preloader });
return contextMap;
}

// currently, `turbo-stream` cannot stream a `ReadableStream`.
// until https://github.com/jacob-ebey/turbo-stream/pull/51
// is merged or similar functionality is added, we need to
Expand Down
Loading
Loading