Skip to content
Merged
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
24 changes: 24 additions & 0 deletions .changeset/strong-parrots-jog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
"react-router": patch
---

Fix a regression in `createRoutesStub` introduced with the middleware feature.

As part of that work we altered the signature to align with the new middleware APIs without making it backwards compatible with the prior `AppLoadContext` API. This permitted `createRoutesStub` to work if you were opting into middleware and the updated `context` typings, but broke `createRoutesStub` for users not yet opting into middleware.

We've reverted this change and re-implemented it in such a way that both sets of users can leverage it.

```tsx
// If you have not opted into middleware, the old API should work again
let context: AppLoadContext = {
/*...*/
};
let Stub = createRoutesStub(routes, context);

// If you have opted into middleware, you should now pass an instantiated `unstable_routerContextProvider` instead of a `getContext` factory function.
let context = new unstable_RouterContextProvider();
context.set(SomeContext, someValue);
let Stub = createRoutesStub(routes, context);
```

⚠️ This may be a breaking bug for if you have adopted the unstable Middleware feature and are using `createRoutesStub` with the updated API.
59 changes: 55 additions & 4 deletions packages/react-router/__tests__/dom/stub-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ import {
type LoaderFunctionArgs,
useRouteError,
} from "../../index";
import { unstable_createContext } from "../../lib/router/utils";
import {
unstable_RouterContextProvider,
unstable_createContext,
} from "../../lib/router/utils";

test("renders a route", () => {
let RoutesStub = createRoutesStub([
Expand Down Expand Up @@ -236,7 +239,50 @@ test("can pass a predefined loader", () => {
]);
});

test("can pass context values", async () => {
test("can pass context values (w/o middleware)", async () => {
let RoutesStub = createRoutesStub(
[
{
path: "/",
HydrateFallback: () => null,
Component() {
let data = useLoaderData() as string;
return (
<div>
<pre data-testid="root">Context: {data}</pre>
<Outlet />
</div>
);
},
loader({ context }) {
return context.message;
},
children: [
{
path: "hello",
Component() {
let data = useLoaderData() as string;
return <pre data-testid="hello">Context: {data}</pre>;
},
loader({ context }) {
return context.message;
},
},
],
},
],
{ message: "hello" }
);

render(<RoutesStub initialEntries={["/hello"]} />);

expect(await screen.findByTestId("root")).toHaveTextContent(/Context: hello/);
expect(await screen.findByTestId("hello")).toHaveTextContent(
/Context: hello/
);
});

test("can pass context values (w/middleware)", async () => {
let helloContext = unstable_createContext();
let RoutesStub = createRoutesStub(
[
Expand Down Expand Up @@ -269,10 +315,15 @@ test("can pass context values", async () => {
],
},
],
() => new Map([[helloContext, "hello"]])
new unstable_RouterContextProvider(new Map([[helloContext, "hello"]]))
);

render(<RoutesStub initialEntries={["/hello"]} />);
render(
<RoutesStub
future={{ unstable_middleware: true }}
initialEntries={["/hello"]}
/>
);

expect(await screen.findByTestId("root")).toHaveTextContent(/Context: hello/);
expect(await screen.findByTestId("hello")).toHaveTextContent(
Expand Down
28 changes: 22 additions & 6 deletions packages/react-router/lib/dom/ssr/routes-test-stub.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import * as React from "react";
import type {
ActionFunction,
ActionFunctionArgs,
LoaderFunction,
unstable_InitialContext,
LoaderFunctionArgs,
} from "../../router/utils";
import type {
DataRouteObject,
Expand All @@ -12,7 +13,12 @@ import type {
import type { LinksFunction, MetaFunction, RouteModules } from "./routeModules";
import type { InitialEntry } from "../../router/history";
import type { HydrationState } from "../../router/router";
import { convertRoutesToDataRoutes } from "../../router/utils";
import {
convertRoutesToDataRoutes,
unstable_RouterContextProvider,
} from "../../router/utils";
import type { MiddlewareEnabled } from "../../types/future";
import type { AppLoadContext } from "../../server-runtime/data";
import type {
AssetsManifest,
FutureConfig,
Expand Down Expand Up @@ -111,7 +117,7 @@ export interface RoutesTestStubProps {
*/
export function createRoutesStub(
routes: StubRouteObject[],
unstable_getContext?: () => unstable_InitialContext
_context?: AppLoadContext | unstable_RouterContextProvider
) {
return function RoutesTestStub({
initialEntries,
Expand Down Expand Up @@ -147,11 +153,15 @@ export function createRoutesStub(
// @ts-expect-error `StubRouteObject` is stricter about `loader`/`action`
// types compared to `AgnosticRouteObject`
convertRoutesToDataRoutes(routes, (r) => r),
_context !== undefined
? _context
: future?.unstable_middleware
? new unstable_RouterContextProvider()
: {},
frameworkContextRef.current.manifest,
frameworkContextRef.current.routeModules
);
routerRef.current = createMemoryRouter(patched, {
unstable_getContext,
initialEntries,
initialIndex,
hydrationData,
Expand All @@ -168,6 +178,7 @@ export function createRoutesStub(

function processRoutes(
routes: StubRouteObject[],
context: unknown,
manifest: AssetsManifest,
routeModules: RouteModules,
parentId?: string
Expand All @@ -192,8 +203,12 @@ function processRoutes(
ErrorBoundary: route.ErrorBoundary
? withErrorBoundaryProps(route.ErrorBoundary)
: undefined,
action: route.action,
loader: route.loader,
action: route.action
? (args: ActionFunctionArgs) => route.action!({ ...args, context })
: undefined,
loader: route.loader
? (args: LoaderFunctionArgs) => route.loader!({ ...args, context })
: undefined,
handle: route.handle,
shouldRevalidate: route.shouldRevalidate,
};
Expand Down Expand Up @@ -235,6 +250,7 @@ function processRoutes(
if (route.children) {
newRoute.children = processRoutes(
route.children,
context,
manifest,
routeModules,
newRoute.id
Expand Down