Skip to content

Commit 84baf0c

Browse files
authored
Update middleware getLoadContext approach (#14097)
1 parent 1b68981 commit 84baf0c

File tree

19 files changed

+121
-100
lines changed

19 files changed

+121
-100
lines changed

.changeset/plenty-cycles-whisper.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"react-router": patch
3+
---
4+
5+
- [UNSTABLE] When middleware is enabled, make the `context` parameter read-only (via `Readonly<unstable_RouterContextProvider>`) so that TypeScript will not allow you to write arbitrary fields to it in loaders, actions, or middleware.

.changeset/shiny-ghosts-happen.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
"@react-router/cloudflare": patch
3+
"@react-router/architect": patch
4+
"@react-router/express": patch
5+
"@react-router/node": patch
6+
"react-router": patch
7+
---
8+
9+
[UNSTABLE] Change `getLoadContext` signature (`type GetLoadContextFunction`) when `future.unstable_middleware` is enabled so that it returns an `unstable_RouterContextProvider` instance instead of a `Map` used to contruct the instance internally
10+
11+
- This also removes the `type unstable_InitialContext` export
12+
- ⚠️ This is a breaking change if you have adopted middleware and are using a custom server with a `getLoadContext` function

.changeset/tiny-turtles-compete.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"react-router": patch
3+
---
4+
5+
[UNSTABLE] Change the `unstable_getContext` signature on `RouterProvider`/`HydratedRouter`/`unstable_RSCHydratedRouter` so that it returns an `unstable_RouterContextProvider` instance instead of a `Map` used to contruct the instance internally
6+
7+
- ⚠️ This is a breaking change if you have adopted the `unstable_getContext` prop

docs/how-to/middleware.md

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -107,17 +107,22 @@ export default function Dashboard({
107107

108108
#### 4. Update your `getLoadContext` function (if applicable)
109109

110-
If you're using a custom server and a `getLoadContext` function, you will need to update your implementation to return a Map of contexts and values, instead of a JavaScript object:
110+
If you're using a custom server and a `getLoadContext` function, you will need to update your implementation to return an instance of `unstable_RouterContextProvider`, instead of a JavaScript object:
111111

112112
```diff
113-
+import { unstable_createContext } from "react-router";
113+
+import {
114+
+ unstable_createContext,
115+
+ unstable_RouterContextProvider,
116+
+} from "react-router";
114117
import { createDb } from "./db";
115-
+
118+
116119
+const dbContext = unstable_createContext<Database>();
117120

118121
function getLoadContext(req, res) {
119122
- return { db: createDb() };
120-
+ const map = new Map([[dbContext, createDb()]]);
123+
+ return new unstable_RouterContextProvider(
124+
+ new Map([[dbContext, createDb()]])
125+
+ );
121126
}
122127
```
123128

@@ -224,58 +229,52 @@ let db = context.get(dbContext);
224229
// ^ Database
225230
```
226231

227-
If you're using a custom server and a `getLoadContext` function, you will need to update your implementation to return a `Map` of contexts and values, instead of a JavaScript object:
232+
If you're using a custom server and a `getLoadContext` function, you will need to update your implementation to return an instance of `unstable_RouterContextProvider`, instead of a plain JavaScript object:
228233

229234
```diff
230-
+import { unstable_createContext } from "react-router";
235+
+import {
236+
+ unstable_createContext,
237+
+ unstable_RouterContextProvider,
238+
+} from "react-router";
231239
import { createDb } from "./db";
232-
+
240+
233241
+const dbContext = unstable_createContext<Database>();
234242

235243
function getLoadContext(req, res) {
236244
- return { db: createDb() };
237-
+ const map = new Map([[dbContext, createDb()]]);
245+
+ return new unstable_RouterContextProvider(
246+
+ new Map([[dbContext, createDb()]])
247+
+ );
238248
}
239249
```
240250

241251
### Migration from `AppLoadContext`
242252

243-
If you're currently using `AppLoadContext`, you can migrate most easily by creating a context for your existing object:
253+
If you're currently using `AppLoadContext`, you can migrate incrementally by using your existing module augmentation to augment `unstable_RouterContextProvider` instead of `AppLoadContext`. Then, update your `getLoadContext` function to return an instance of `unstable_RouterContextProvider`:
244254

245-
```ts filename=app/context.ts
246-
import { unstable_createContext } from "react-router";
247-
248-
declare module "@react-router/server-runtime" {
249-
interface AppLoadContext {
255+
```diff
256+
declare module "react-router" {
257+
- interface AppLoadContext {
258+
+ interface unstable_RouterContextProvider {
250259
db: Database;
251260
user: User;
252261
}
253262
}
254263

255-
const myLoadContext =
256-
unstable_createContext<AppLoadContext>();
257-
```
258-
259-
Update your `getLoadContext` function to return a Map with the context initial value:
260-
261-
```diff filename=server.ts
262264
function getLoadContext() {
263265
const loadContext = {...};
264266
- return loadContext;
265-
+ return new Map([
266-
+ [myLoadContext, loadContext]]
267-
+ );
267+
+ let context = new unstable_RouterContextProvider();
268+
+ Object.assign(context, loadContext);
269+
+ return context;
268270
}
269271
```
270272

271-
Update your loaders/actions to read from the new context instance:
273+
This allows you to leave your loaders/actions untouched during initial adoption of middleware, since they can still read values directly (i.e., `context.db`).
272274

273-
```diff filename=app/routes/example.tsx
274-
export function loader({ context }: Route.LoaderArgs) {
275-
- const { db, user } = context;
276-
+ const { db, user } = context.get(myLoadContext);
277-
}
278-
```
275+
<docs-warning>This approach is only intended to be used as a migration strategy when adopting middleware in React Router v7, allowing you to incrementally migrate to `context.set`/`context.get`. It is not safe to assume this approach will work in the next major version of React Router.</docs-warning>
276+
277+
<docs-warning>The `unstable_RouterContextProvider` class is also used for the client-side `context` parameter via `<HydratedRouter unstable_getContext>` and `<RouterProvider unstable_getContext>`. Since `AppLoadContext` is primarily intended as a hand-off from your HTTP server into the React Router handlers, you need to be aware that these augmented fields will not be available in `clientMiddleware`, `clientLoader`, or `clientAction` functions even thought TypeScript will tell you they are (unless, of course, you provide the fields via `unstable_getContext` on the client).</docs-warning>
279278

280279
## Common Patterns
281280

integration/browser-entry-test.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -87,29 +87,33 @@ test("allows users to pass a client side context to HydratedRouter", async ({
8787
let fixture = await createFixture({
8888
files: {
8989
"app/entry.client.tsx": js`
90-
import { unstable_createContext } from "react-router";
90+
import { unstable_createContext, unstable_RouterContextProvider } from "react-router";
9191
import { HydratedRouter } from "react-router/dom";
9292
import { startTransition, StrictMode } from "react";
9393
import { hydrateRoot } from "react-dom/client";
9494
95-
export const initialContext = new unstable_createContext('empty');
95+
export const myContext = new unstable_createContext('foo');
9696
9797
startTransition(() => {
9898
hydrateRoot(
9999
document,
100100
<StrictMode>
101-
<HydratedRouter unstable_getContext={() => {
102-
return new Map([[initialContext, 'bar']]);
103-
}} />
101+
<HydratedRouter
102+
unstable_getContext={() => {
103+
return new unstable_RouterContextProvider([
104+
[myContext, 'bar']
105+
]);
106+
}}
107+
/>
104108
</StrictMode>
105109
);
106110
});
107111
`,
108112
"app/routes/_index.tsx": js`
109-
import { initialContext } from "../entry.client";
113+
import { myContext } from "../entry.client";
110114
111115
export function clientLoader({ context }) {
112-
return context.get(initialContext);
116+
return context.get(myContext);
113117
}
114118
export default function Index({ loaderData }) {
115119
return <h1>Hello, {loaderData}</h1>

packages/react-router-architect/server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type {
22
AppLoadContext,
33
UNSAFE_MiddlewareEnabled as MiddlewareEnabled,
44
ServerBuild,
5-
unstable_InitialContext,
5+
unstable_RouterContextProvider,
66
} from "react-router";
77
import { createRequestHandler as createReactRouterRequestHandler } from "react-router";
88
import { readableStreamToString } from "@react-router/node";
@@ -27,7 +27,7 @@ type MaybePromise<T> = T | Promise<T>;
2727
export type GetLoadContextFunction = (
2828
event: APIGatewayProxyEventV2,
2929
) => MiddlewareEnabled extends true
30-
? MaybePromise<unstable_InitialContext>
30+
? MaybePromise<unstable_RouterContextProvider>
3131
: MaybePromise<AppLoadContext>;
3232

3333
export type RequestHandler = APIGatewayProxyHandlerV2;

packages/react-router-cloudflare/worker.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type {
22
AppLoadContext,
33
UNSAFE_MiddlewareEnabled as MiddlewareEnabled,
44
ServerBuild,
5-
unstable_InitialContext,
5+
unstable_RouterContextProvider,
66
} from "react-router";
77
import { createRequestHandler as createReactRouterRequestHandler } from "react-router";
88
import { type CacheStorage } from "@cloudflare/workers-types";
@@ -37,7 +37,7 @@ export type GetLoadContextFunction<
3737
};
3838
};
3939
}) => MiddlewareEnabled extends true
40-
? MaybePromise<unstable_InitialContext>
40+
? MaybePromise<unstable_RouterContextProvider>
4141
: MaybePromise<AppLoadContext>;
4242

4343
export type RequestHandler<Env = any> = PagesFunction<Env>;

packages/react-router-dev/vite/cloudflare-dev-proxy.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ import {
33
type AppLoadContext,
44
type ServerBuild,
55
type UNSAFE_MiddlewareEnabled,
6-
type unstable_InitialContext,
6+
type unstable_RouterContextProvider,
77
} from "react-router";
88
import { type Plugin } from "vite";
99
import { type GetPlatformProxyOptions, type PlatformProxy } from "wrangler";
1010

1111
import { fromNodeRequest, toNodeRequest } from "./node-adapter";
12-
import { preloadVite, getVite } from "./vite";
12+
import { preloadVite } from "./vite";
1313
import { type ResolvedReactRouterConfig, loadConfig } from "../config/config";
1414

1515
let serverBuildId = "virtual:react-router/server-build";
@@ -26,7 +26,7 @@ type GetLoadContext<Env, Cf extends CfProperties> = (args: {
2626
request: Request;
2727
context: LoadContext<Env, Cf>;
2828
}) => UNSAFE_MiddlewareEnabled extends true
29-
? MaybePromise<unstable_InitialContext>
29+
? MaybePromise<unstable_RouterContextProvider>
3030
: MaybePromise<AppLoadContext>;
3131

3232
function importWrangler() {

packages/react-router-dev/vite/plugin.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import type {
2525
ServerBuild,
2626
DataRouteObject,
2727
UNSAFE_MiddlewareEnabled as MiddlewareEnabled,
28-
unstable_InitialContext,
28+
unstable_RouterContextProvider,
2929
} from "react-router";
3030
import {
3131
init as initEsModuleLexer,
@@ -614,7 +614,7 @@ let reactRouterDevLoadContext: (
614614
request: Request,
615615
) => MaybePromise<
616616
MiddlewareEnabled extends true
617-
? MaybePromise<unstable_InitialContext | undefined>
617+
? MaybePromise<unstable_RouterContextProvider | undefined>
618618
: MaybePromise<Record<string, unknown> | undefined>
619619
> = () => undefined;
620620

packages/react-router-express/server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type {
77
AppLoadContext,
88
ServerBuild,
99
UNSAFE_MiddlewareEnabled as MiddlewareEnabled,
10-
unstable_InitialContext,
10+
unstable_RouterContextProvider,
1111
} from "react-router";
1212
import { createRequestHandler as createRemixRequestHandler } from "react-router";
1313
import {
@@ -29,7 +29,7 @@ export type GetLoadContextFunction = (
2929
req: express.Request,
3030
res: express.Response,
3131
) => MiddlewareEnabled extends true
32-
? MaybePromise<unstable_InitialContext>
32+
? MaybePromise<unstable_RouterContextProvider>
3333
: MaybePromise<AppLoadContext>;
3434

3535
export type RequestHandler = (

0 commit comments

Comments
 (0)