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
2 changes: 1 addition & 1 deletion docs/explanation/hot-module-replacement.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export default function Component({ loaderData }) {

If you change the key `pet` to `dog`:

```diff
```tsx diff
export default function Component() {
- const { pet } = useMyCustomHook();
+ const { dog } = useMyCustomHook();
Expand Down
2 changes: 1 addition & 1 deletion docs/how-to/data-strategy.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ With `shouldCallHandler`, you are in charge of which handlers should be called s

Here's an example change from the prior API to the new API. Note that we pre-filter the `matchesToLoad` before calling `resolve()`:

```diff
```tsx diff
let results = {};
+let matchesToLoad = matches.filter(m => m.shouldCallHandler());
await Promise.all(() =>
Expand Down
2 changes: 1 addition & 1 deletion docs/upgrading/component-routes.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ npm install @react-router/node

**👉 Swap out the React plugin for React Router.**

```diff filename=vite.config.ts
```tsx diff filename=vite.config.ts
-import react from '@vitejs/plugin-react'
+import { reactRouter } from "@react-router/dev/vite";
import { defineConfig } from "vite";
Expand Down
10 changes: 5 additions & 5 deletions docs/upgrading/router-provider.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ Instead of importing your route modules directly, lazy load and convert them to

Not only does your route definition now conform to the Route Module API, but you also get the benefits of code-splitting your routes.

```diff filename=src/main.tsx
```tsx diff filename=src/main.tsx
let router = createBrowserRouter([
// ... other routes
{
Expand Down Expand Up @@ -120,7 +120,7 @@ npm install @react-router/node

**👉 Swap out the React plugin for React Router**

```diff filename=vite.config.ts
```tsx diff filename=vite.config.ts
-import react from '@vitejs/plugin-react'
+import { reactRouter } from "@react-router/dev/vite";
import { defineConfig } from "vite";
Expand Down Expand Up @@ -247,7 +247,7 @@ export default function App() {

You would move everything above the `RouterProvider` into `root.tsx`.

```diff filename=src/root.tsx
```tsx diff filename=src/root.tsx
+import "./index.css";

// ... other imports and Layout
Expand Down Expand Up @@ -326,7 +326,7 @@ touch src/routes.ts src/catchall.tsx

Move your route definitions to `routes.ts`. Note that the schemas don't match exactly, so you will get type errors; we'll fix this next.

```diff filename=src/routes.ts
```tsx diff filename=src/routes.ts
+import type { RouteConfig } from "@react-router/dev/routes";

-const router = createBrowserRouter([
Expand Down Expand Up @@ -362,7 +362,7 @@ Move your route definitions to `routes.ts`. Note that the schemas don't match ex

**👉 Replace the `lazy` loader with a `file` loader**

```diff filename=src/routes.ts
```tsx diff filename=src/routes.ts
export default [
{
path: "/",
Expand Down
14 changes: 7 additions & 7 deletions docs/upgrading/v7.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ Most users won't need to make any changes. However, if you have custom Vite conf

For example, a custom server build should move its SSR `rollupOptions` from the top-level `build` config into `environments.ssr.build`:

```diff filename=vite.config.ts
```tsx diff filename=vite.config.ts
import { reactRouter } from "@react-router/dev/vite";
import { defineConfig } from "vite";

Expand Down Expand Up @@ -310,7 +310,7 @@ Use `loaderData` instead on `MetaArgs`, each item in `MetaArgs.matches`, and eac

Replace `data` with `loaderData` in your `meta` functions:

```diff
```tsx diff
export function meta({
- data,
+ loaderData,
Expand All @@ -327,7 +327,7 @@ export function meta({

If you read data from parent matches, update those references too:

```diff
```tsx diff
export function meta({ matches }: Route.MetaArgs) {
let rootMatch = matches.find((match) => match.id === "root");
- let rootData = rootMatch?.data;
Expand All @@ -339,7 +339,7 @@ export function meta({ matches }: Route.MetaArgs) {

Replace `data` with `loaderData` on `useMatches()` calls:

```diff
```tsx diff
export default function Component({ matches, loaderData }: ComponentProps) {
let matches = useMatches();
- const rootLoaderData = matches[0].data;
Expand Down Expand Up @@ -369,14 +369,14 @@ npm uninstall react-router-dom

Replace `react-router-dom` imports with `react-router` imports:

```diff
```tsx diff
-import { Link, useLocation } from "react-router-dom";
+import { Link, useLocation } from "react-router";
```

For DOM-specific APIs, import from `react-router/dom`:

```diff
```tsx diff
-import { RouterProvider } from "react-router-dom";
+import { RouterProvider } from "react-router/dom";
```
Expand All @@ -396,7 +396,7 @@ React Router v8 removes the React Router Cloudflare dev proxy. Cloudflare projec

Replace `cloudflareDevProxy` with `cloudflare`:

```diff filename=vite.config.ts
```tsx diff filename=vite.config.ts
import { reactRouter } from "@react-router/dev/vite";
-import { cloudflareDevProxy } from "@react-router/dev/vite/cloudflare";
+import { cloudflare } from "@cloudflare/vite-plugin";
Expand Down
4 changes: 1 addition & 3 deletions integration/helpers/rsc-vite/src/entry.browser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ setServerCallback(

createFromReadableStream<RSCPayload>(getRSCStream()).then((payload) => {
startTransition(async () => {
const formState =
payload.type === "render" ? await payload.formState : undefined;
const formState = payload.type === "render" ? payload.formState : undefined;

hydrateRoot(
document,
Expand All @@ -37,7 +36,6 @@ createFromReadableStream<RSCPayload>(getRSCStream()).then((payload) => {
/>
</StrictMode>,
{
// @ts-expect-error - no types for this yet
formState,
},
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Preserve navigation blocker state through a revalidation
1 change: 1 addition & 0 deletions packages/react-router/.changes/patch.rsc-form-state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Update to use ReactFormState types instead of unknown.
13 changes: 13 additions & 0 deletions packages/react-router/__tests__/router/navigation-blocking-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,19 @@ describe("navigation blocking", () => {
expect(router.state.location.pathname).toBe(pathnameBeforeNavigation);
});
});

describe("revalidation while blocked", () => {
let fn = () => true;
it("preserves a 'blocked' blocker through a revalidation", async () => {
router.getBlocker("KEY", fn);
await router.navigate("/about");
expect(router.getBlocker("KEY", fn).state).toBe("blocked");

// a revalidation is not a navigation, so it must not clear the blocker
await router.revalidate();
expect(router.getBlocker("KEY", fn).state).toBe("blocked");
});
});
});

describe("on history replace", () => {
Expand Down
5 changes: 3 additions & 2 deletions packages/react-router/lib/router/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1544,9 +1544,10 @@ export function createRouter(init: RouterInit): Router {
: state.loaderData;

// On a successful navigation we can assume we got through all blockers
// so we can start fresh
// so we can start fresh. A revalidation is not a navigation, so it must
// leave any active blocker untouched.
let blockers = state.blockers;
if (blockers.size > 0) {
if (blockers.size > 0 && !isUninterruptedRevalidation) {
blockers = new Map(blockers);
blockers.forEach((_, k) => blockers.set(k, IDLE_BLOCKER));
}
Expand Down
13 changes: 7 additions & 6 deletions packages/react-router/lib/rsc/server.rsc.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// eslint-disable-next-line import/no-nodejs-modules
import { AsyncLocalStorage } from "node:async_hooks";
import * as React from "react";
import type { ReactFormState } from "react-dom/client";

import type {
ClientActionFunction,
Expand Down Expand Up @@ -253,7 +254,7 @@ export type RSCRenderPayload = {
// for SPA navigations the manifest call will handle these patches.
patches?: Promise<RSCRouteManifest[]>;
nonce?: string;
formState?: unknown;
formState?: ReactFormState;
};

export type RSCManifestPayload = {
Expand Down Expand Up @@ -296,7 +297,7 @@ export type DecodeActionFunction = (
export type DecodeFormStateFunction = (
result: unknown,
formData: FormData,
) => unknown;
) => Promise<ReactFormState | undefined>;

export type DecodeReplyFunction = (
reply: FormData | string,
Expand Down Expand Up @@ -638,7 +639,7 @@ async function processServerAction(
skipRevalidation: boolean;
revalidationRequest: Request;
actionResult?: Promise<unknown>;
formState?: unknown;
formState?: ReactFormState;
}
| Response
| undefined
Expand Down Expand Up @@ -711,7 +712,7 @@ async function processServerAction(
if (isRedirectResponse(result)) {
result = prependBasenameToRedirectResponse(result, basename);
}
formState = decodeFormState?.(result, formData);
formState = await decodeFormState?.(result, formData);
} catch (error) {
if (isRedirectResponse(error)) {
return prependBasenameToRedirectResponse(error, basename);
Expand Down Expand Up @@ -850,7 +851,7 @@ async function generateRenderResponse(
// revalidation. If this is a RR Form/Fetcher submission,
// `processServerAction` will fall through as a no-op and we'll pass the
// POST `request` to `query` and process our action there.
let formState: unknown;
let formState: ReactFormState | undefined;
let skipRevalidation = false;
let potentialCSRFAttackError: unknown | undefined;
if (isMutationMethod(request.method)) {
Expand Down Expand Up @@ -1039,7 +1040,7 @@ async function generateStaticContextResponse(
isDataRequest: boolean,
isSubmission: boolean,
actionResult: Promise<unknown> | undefined,
formState: unknown | undefined,
formState: ReactFormState | undefined,
staticContext: StaticHandlerContext,
temporaryReferences: unknown,
skipRevalidation: boolean,
Expand Down
1 change: 0 additions & 1 deletion playground/rsc-vite-framework/app/entry.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ createFromReadableStream<RSCPayload>(getRSCStream()).then((payload) => {
/>
</StrictMode>,
{
// @ts-expect-error - on 18 types, requires 19. --- IGNORE ---
formState,
},
);
Expand Down
2 changes: 0 additions & 2 deletions playground/rsc-vite-framework/app/entry.ssr.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { createFromReadableStream } from "@vitejs/plugin-rsc/ssr";
// @ts-ignore - on 18 types, requires 19
import { renderToReadableStream } from "react-dom/server.edge";
import {
unstable_routeRSCServerRequest as routeRSCServerRequest,
Expand Down Expand Up @@ -32,7 +31,6 @@ export async function generateHTML(
{
...options,
bootstrapScriptContent,
// @ts-expect-error - no types for this yet
formState,
signal: request.signal,
},
Expand Down
1 change: 0 additions & 1 deletion playground/rsc-vite/src/entry.browser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ createFromReadableStream<RSCPayload>(getRSCStream()).then((payload) => {
/>
</StrictMode>,
{
// @ts-expect-error - no types for this yet
formState,
},
);
Expand Down