// default ("render")
+
+
```
-- **render** — default, discover the route when the link renders
-- **none** — don't eagerly discover, only discover if the link is clicked
-
### encType
The encoding type to use for the form submission.
diff --git a/docs/api/components/Link.md b/docs/api/components/Link.md
index ca8ea63f4a..db4ae3033e 100644
--- a/docs/api/components/Link.md
+++ b/docs/api/components/Link.md
@@ -45,7 +45,10 @@ import { Link } from "react-router";
[modes: framework]
-Defines the link discovery behavior
+Defines the link [lazy route discovery](../../explanation/lazy-route-discovery) behavior.
+
+- **render** — default, discover the route when the link renders
+- **none** — don't eagerly discover, only discover if the link is clicked
```tsx
// default ("render")
@@ -53,9 +56,6 @@ Defines the link discovery behavior
```
-- **render** — default, discover the route when the link renders
-- **none** — don't eagerly discover, only discover if the link is clicked
-
### prefetch
[modes: framework]
diff --git a/docs/api/components/NavLink.md b/docs/api/components/NavLink.md
index 34ca860662..8a763dbcfd 100644
--- a/docs/api/components/NavLink.md
+++ b/docs/api/components/NavLink.md
@@ -109,7 +109,10 @@ returns the `className`:
[modes: framework]
-Defines the link discovery behavior
+Defines the link [lazy route discovery](../../explanation/lazy-route-discovery) behavior.
+
+- **render** — default, discover the route when the link renders
+- **none** — don't eagerly discover, only discover if the link is clicked
```tsx
// default ("render")
@@ -117,9 +120,6 @@ Defines the link discovery behavior
```
-- **render** — default, discover the route when the link renders
-- **none** — don't eagerly discover, only discover if the link is clicked
-
### end
[modes: framework, data, declarative]
diff --git a/docs/api/data-routers/createBrowserRouter.md b/docs/api/data-routers/createBrowserRouter.md
index ff0ca8c1bd..185f482fe2 100644
--- a/docs/api/data-routers/createBrowserRouter.md
+++ b/docs/api/data-routers/createBrowserRouter.md
@@ -47,189 +47,32 @@ Basename path for the application.
### opts.dataStrategy
-Override the default data strategy of running loaders in parallel.
-See [`DataStrategyFunction`](https://api.reactrouter.com/v7/interfaces/react_router.DataStrategyFunction.html).
-
-This is a low-level API intended for advanced use-cases. This
-overrides React Router's internal handling of
-[`action`](../../start/data/route-object#action)/[`loader`](../../start/data/route-object#loader)
-execution, and if done incorrectly will break your app code. Please use
-with caution and perform the appropriate testing.
-
-By default, React Router is opinionated about how your data is loaded/submitted -
-and most notably, executes all of your [`loader`](../../start/data/route-object#loader)s
-in parallel for optimal data fetching. While we think this is the right
-behavior for most use-cases, we realize that there is no "one size fits all"
-solution when it comes to data fetching for the wide landscape of
-application requirements.
-
-The `dataStrategy` option gives you full control over how your [`action`](../../start/data/route-object#action)s
-and [`loader`](../../start/data/route-object#loader)s are executed and lays
-the foundation to build in more advanced APIs such as middleware, context,
-and caching layers. Over time, we expect that we'll leverage this API
-internally to bring more first class APIs to React Router, but until then
-(and beyond), this is your way to add more advanced functionality for your
-application's data needs.
-
-The `dataStrategy` function should return a key/value-object of
-`routeId` -> [`DataStrategyResult`](https://api.reactrouter.com/v7/interfaces/react_router.DataStrategyResult.html) and should include entries for any
-routes where a handler was executed. A `DataStrategyResult` indicates if
-the handler was successful or not based on the `DataStrategyResult.type`
-field. If the returned `DataStrategyResult.result` is a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response),
-React Router will unwrap it for you (via [`res.json`](https://developer.mozilla.org/en-US/docs/Web/API/Response/json)
-or [`res.text`](https://developer.mozilla.org/en-US/docs/Web/API/Response/text)).
-If you need to do custom decoding of a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)
-but want to preserve the status code, you can use the `data` utility to
-return your decoded data along with a `ResponseInit`.
-
-
-Example dataStrategy Use Cases
-
-**Adding logging**
-
-In the simplest case, let's look at hooking into this API to add some logging
-for when our route [`action`](../../start/data/route-object#action)s/[`loader`](../../start/data/route-object#loader)s
-execute:
+Override the default data strategy of running loaders in parallel -
+see the [docs](../../how-to/data-strategy) for more information.
```tsx
let router = createBrowserRouter(routes, {
- async dataStrategy({ matches, request }) {
- const matchesToLoad = matches.filter((m) => m.shouldLoad);
- const results: Record = {};
- await Promise.all(
- matchesToLoad.map(async (match) => {
- console.log(`Processing ${match.route.id}`);
- results[match.route.id] = await match.resolve();;
- })
+ async dataStrategy({
+ matches,
+ request,
+ runClientMiddleware,
+ }) {
+ const matchesToLoad = matches.filter((m) =>
+ m.shouldCallHandler(),
);
- return results;
- },
-});
-```
-
-**Middleware**
-Let's define a middleware on each route via [`handle`](../../start/data/route-object#handle)
-and call middleware sequentially first, then call all
-[`loader`](../../start/data/route-object#loader)s in parallel - providing
-any data made available via the middleware:
-
-```ts
-const routes = [
- {
- id: "parent",
- path: "/parent",
- loader({ request }, context) {
- // ...
- },
- handle: {
- async middleware({ request }, context) {
- context.parent = "PARENT MIDDLEWARE";
- },
- },
- children: [
- {
- id: "child",
- path: "child",
- loader({ request }, context) {
- // ...
- },
- handle: {
- async middleware({ request }, context) {
- context.child = "CHILD MIDDLEWARE";
- },
- },
- },
- ],
- },
-];
-
-let router = createBrowserRouter(routes, {
- async dataStrategy({ matches, params, request }) {
- // Run middleware sequentially and let them add data to `context`
- let context = {};
- for (const match of matches) {
- if (match.route.handle?.middleware) {
- await match.route.handle.middleware(
- { request, params },
- context
- );
- }
- }
-
- // Run loaders in parallel with the `context` value
- let matchesToLoad = matches.filter((m) => m.shouldLoad);
- let results = await Promise.all(
- matchesToLoad.map((match, i) =>
- match.resolve((handler) => {
- // Whatever you pass to `handler` will be passed as the 2nd parameter
- // to your loader/action
- return handler(context);
- })
- )
- );
- return results.reduce(
- (acc, result, i) =>
- Object.assign(acc, {
- [matchesToLoad[i].route.id]: result,
+ const results: Record = {};
+ await runClientMiddleware(() =>
+ Promise.all(
+ matchesToLoad.map(async (match) => {
+ results[match.route.id] = await match.resolve();
}),
- {}
+ ),
);
- },
-});
-```
-
-**Custom Handler**
-
-It's also possible you don't even want to define a [`loader`](../../start/data/route-object#loader)
-implementation at the route level. Maybe you want to just determine the
-routes and issue a single GraphQL request for all of your data? You can do
-that by setting your `route.loader=true` so it qualifies as "having a
-loader", and then store GQL fragments on `route.handle`:
-
-```ts
-const routes = [
- {
- id: "parent",
- path: "/parent",
- loader: true,
- handle: {
- gql: gql`
- fragment Parent on Whatever {
- parentField
- }
- `,
- },
- children: [
- {
- id: "child",
- path: "child",
- loader: true,
- handle: {
- gql: gql`
- fragment Child on Whatever {
- childField
- }
- `,
- },
- },
- ],
- },
-];
-
-let router = createBrowserRouter(routes, {
- async dataStrategy({ matches, params, request }) {
- // Compose route fragments into a single GQL payload
- let gql = getFragmentsFromRouteHandles(matches);
- let data = await fetchGql(gql);
- // Parse results back out into individual route level `DataStrategyResult`'s
- // keyed by `routeId`
- let results = parseResultsFromGql(data);
return results;
},
});
```
-
### opts.future
@@ -336,7 +179,7 @@ individual routes prior to router initialization (and on any subsequently
added routes via `route.lazy` or `patchRoutesOnNavigation`). This is
mostly useful for observability such as wrapping navigations, fetches,
as well as route loaders/actions/middlewares with logging and/or performance
-tracing.
+tracing. See the [docs](../../how-to/instrumentation) for more information.
```tsx
let router = createBrowserRouter(routes, {
diff --git a/docs/api/data-routers/createHashRouter.md b/docs/api/data-routers/createHashRouter.md
index 427b74a9ec..d95832da07 100644
--- a/docs/api/data-routers/createHashRouter.md
+++ b/docs/api/data-routers/createHashRouter.md
@@ -149,7 +149,7 @@ individual routes prior to router initialization (and on any subsequently
added routes via `route.lazy` or `patchRoutesOnNavigation`). This is
mostly useful for observability such as wrapping navigations, fetches,
as well as route loaders/actions/middlewares with logging and/or performance
-tracing.
+tracing. See the [docs](../../how-to/instrumentation) for more information.
```tsx
let router = createBrowserRouter(routes, {
@@ -193,189 +193,32 @@ async function logExecution(label: string, impl: () => Promise) {
### opts.dataStrategy
-Override the default data strategy of running loaders in parallel.
-See [`DataStrategyFunction`](https://api.reactrouter.com/v7/interfaces/react_router.DataStrategyFunction.html).
-
-This is a low-level API intended for advanced use-cases. This
-overrides React Router's internal handling of
-[`action`](../../start/data/route-object#action)/[`loader`](../../start/data/route-object#loader)
-execution, and if done incorrectly will break your app code. Please use
-with caution and perform the appropriate testing.
-
-By default, React Router is opinionated about how your data is loaded/submitted -
-and most notably, executes all of your [`loader`](../../start/data/route-object#loader)s
-in parallel for optimal data fetching. While we think this is the right
-behavior for most use-cases, we realize that there is no "one size fits all"
-solution when it comes to data fetching for the wide landscape of
-application requirements.
-
-The `dataStrategy` option gives you full control over how your [`action`](../../start/data/route-object#action)s
-and [`loader`](../../start/data/route-object#loader)s are executed and lays
-the foundation to build in more advanced APIs such as middleware, context,
-and caching layers. Over time, we expect that we'll leverage this API
-internally to bring more first class APIs to React Router, but until then
-(and beyond), this is your way to add more advanced functionality for your
-application's data needs.
-
-The `dataStrategy` function should return a key/value-object of
-`routeId` -> [`DataStrategyResult`](https://api.reactrouter.com/v7/interfaces/react_router.DataStrategyResult.html) and should include entries for any
-routes where a handler was executed. A `DataStrategyResult` indicates if
-the handler was successful or not based on the `DataStrategyResult.type`
-field. If the returned `DataStrategyResult.result` is a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response),
-React Router will unwrap it for you (via [`res.json`](https://developer.mozilla.org/en-US/docs/Web/API/Response/json)
-or [`res.text`](https://developer.mozilla.org/en-US/docs/Web/API/Response/text)).
-If you need to do custom decoding of a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)
-but want to preserve the status code, you can use the `data` utility to
-return your decoded data along with a `ResponseInit`.
-
-
-Example dataStrategy Use Cases
-
-**Adding logging**
-
-In the simplest case, let's look at hooking into this API to add some logging
-for when our route [`action`](../../start/data/route-object#action)s/[`loader`](../../start/data/route-object#loader)s
-execute:
+Override the default data strategy of running loaders in parallel -
+see the [docs](../../how-to/data-strategy) for more information.
```tsx
let router = createBrowserRouter(routes, {
- async dataStrategy({ matches, request }) {
- const matchesToLoad = matches.filter((m) => m.shouldLoad);
- const results: Record = {};
- await Promise.all(
- matchesToLoad.map(async (match) => {
- console.log(`Processing ${match.route.id}`);
- results[match.route.id] = await match.resolve();;
- })
+ async dataStrategy({
+ matches,
+ request,
+ runClientMiddleware,
+ }) {
+ const matchesToLoad = matches.filter((m) =>
+ m.shouldCallHandler(),
);
- return results;
- },
-});
-```
-
-**Middleware**
-Let's define a middleware on each route via [`handle`](../../start/data/route-object#handle)
-and call middleware sequentially first, then call all
-[`loader`](../../start/data/route-object#loader)s in parallel - providing
-any data made available via the middleware:
-
-```ts
-const routes = [
- {
- id: "parent",
- path: "/parent",
- loader({ request }, context) {
- // ...
- },
- handle: {
- async middleware({ request }, context) {
- context.parent = "PARENT MIDDLEWARE";
- },
- },
- children: [
- {
- id: "child",
- path: "child",
- loader({ request }, context) {
- // ...
- },
- handle: {
- async middleware({ request }, context) {
- context.child = "CHILD MIDDLEWARE";
- },
- },
- },
- ],
- },
-];
-
-let router = createBrowserRouter(routes, {
- async dataStrategy({ matches, params, request }) {
- // Run middleware sequentially and let them add data to `context`
- let context = {};
- for (const match of matches) {
- if (match.route.handle?.middleware) {
- await match.route.handle.middleware(
- { request, params },
- context
- );
- }
- }
-
- // Run loaders in parallel with the `context` value
- let matchesToLoad = matches.filter((m) => m.shouldLoad);
- let results = await Promise.all(
- matchesToLoad.map((match, i) =>
- match.resolve((handler) => {
- // Whatever you pass to `handler` will be passed as the 2nd parameter
- // to your loader/action
- return handler(context);
- })
- )
- );
- return results.reduce(
- (acc, result, i) =>
- Object.assign(acc, {
- [matchesToLoad[i].route.id]: result,
+ const results: Record = {};
+ await runClientMiddleware(() =>
+ Promise.all(
+ matchesToLoad.map(async (match) => {
+ results[match.route.id] = await match.resolve();
}),
- {}
+ ),
);
- },
-});
-```
-
-**Custom Handler**
-
-It's also possible you don't even want to define a [`loader`](../../start/data/route-object#loader)
-implementation at the route level. Maybe you want to just determine the
-routes and issue a single GraphQL request for all of your data? You can do
-that by setting your `route.loader=true` so it qualifies as "having a
-loader", and then store GQL fragments on `route.handle`:
-
-```ts
-const routes = [
- {
- id: "parent",
- path: "/parent",
- loader: true,
- handle: {
- gql: gql`
- fragment Parent on Whatever {
- parentField
- }
- `,
- },
- children: [
- {
- id: "child",
- path: "child",
- loader: true,
- handle: {
- gql: gql`
- fragment Child on Whatever {
- childField
- }
- `,
- },
- },
- ],
- },
-];
-
-let router = createBrowserRouter(routes, {
- async dataStrategy({ matches, params, request }) {
- // Compose route fragments into a single GQL payload
- let gql = getFragmentsFromRouteHandles(matches);
- let data = await fetchGql(gql);
- // Parse results back out into individual route level `DataStrategyResult`'s
- // keyed by `routeId`
- let results = parseResultsFromGql(data);
return results;
},
});
```
-
### opts.patchRoutesOnNavigation
diff --git a/docs/api/data-routers/createMemoryRouter.md b/docs/api/data-routers/createMemoryRouter.md
index 28b2e37c48..c8bccee7d4 100644
--- a/docs/api/data-routers/createMemoryRouter.md
+++ b/docs/api/data-routers/createMemoryRouter.md
@@ -47,8 +47,32 @@ Basename path for the application.
### opts.dataStrategy
-Override the default data strategy of loading in parallel.
-Only intended for advanced usage.
+Override the default data strategy of running loaders in parallel -
+see the [docs](../../how-to/data-strategy) for more information.
+
+```tsx
+let router = createBrowserRouter(routes, {
+ async dataStrategy({
+ matches,
+ request,
+ runClientMiddleware,
+ }) {
+ const matchesToLoad = matches.filter((m) =>
+ m.shouldCallHandler(),
+ );
+
+ const results: Record = {};
+ await runClientMiddleware(() =>
+ Promise.all(
+ matchesToLoad.map(async (match) => {
+ results[match.route.id] = await match.resolve();
+ }),
+ ),
+ );
+ return results;
+ },
+});
+```
### opts.future
@@ -82,7 +106,7 @@ individual routes prior to router initialization (and on any subsequently
added routes via `route.lazy` or `patchRoutesOnNavigation`). This is
mostly useful for observability such as wrapping navigations, fetches,
as well as route loaders/actions/middlewares with logging and/or performance
-tracing.
+tracing. See the [docs](../../how-to/instrumentation) for more information.
```tsx
let router = createBrowserRouter(routes, {
diff --git a/docs/api/hooks/useNavigate.md b/docs/api/hooks/useNavigate.md
index 58de33ba20..ffc0cf2a2d 100644
--- a/docs/api/hooks/useNavigate.md
+++ b/docs/api/hooks/useNavigate.md
@@ -78,14 +78,12 @@ navigate("/some/route?search=param");
All properties are optional.
```tsx
-navigate(
- {
- pathname: "/some/route",
- search: "?search=param",
- hash: "#hash",
- },
- { state: { some: "state" } }
-);
+navigate({
+ pathname: "/some/route",
+ search: "?search=param",
+ hash: "#hash",
+ state: { some: "state" },
+});
```
If you use `state`, that will be available on the [`Location`](https://api.reactrouter.com/v7/interfaces/react_router.Location.html) object on
diff --git a/packages/create-react-router/CHANGELOG.md b/packages/create-react-router/CHANGELOG.md
index b1a09daa0e..c7e77928c5 100644
--- a/packages/create-react-router/CHANGELOG.md
+++ b/packages/create-react-router/CHANGELOG.md
@@ -1,5 +1,9 @@
# `create-react-router`
+## 7.10.1
+
+_No changes_
+
## 7.10.0
_No changes_
diff --git a/packages/create-react-router/package.json b/packages/create-react-router/package.json
index da5a573821..36ad0775d1 100644
--- a/packages/create-react-router/package.json
+++ b/packages/create-react-router/package.json
@@ -1,6 +1,6 @@
{
"name": "create-react-router",
- "version": "7.10.0",
+ "version": "7.10.1",
"description": "Create a new React Router app",
"homepage": "https://reactrouter.com",
"bugs": {
diff --git a/packages/react-router-architect/CHANGELOG.md b/packages/react-router-architect/CHANGELOG.md
index 3bae066134..79ee31da68 100644
--- a/packages/react-router-architect/CHANGELOG.md
+++ b/packages/react-router-architect/CHANGELOG.md
@@ -1,5 +1,13 @@
# `@react-router/architect`
+## 7.10.1
+
+### Patch Changes
+
+- Updated dependencies:
+ - `react-router@7.10.1`
+ - `@react-router/node@7.10.1`
+
## 7.10.0
### Patch Changes
diff --git a/packages/react-router-architect/package.json b/packages/react-router-architect/package.json
index 0862e92715..f1fad143e1 100644
--- a/packages/react-router-architect/package.json
+++ b/packages/react-router-architect/package.json
@@ -1,6 +1,6 @@
{
"name": "@react-router/architect",
- "version": "7.10.0",
+ "version": "7.10.1",
"description": "Architect server request handler for React Router",
"bugs": {
"url": "https://github.com/remix-run/react-router/issues"
diff --git a/packages/react-router-cloudflare/CHANGELOG.md b/packages/react-router-cloudflare/CHANGELOG.md
index ade7635d77..8862fa823e 100644
--- a/packages/react-router-cloudflare/CHANGELOG.md
+++ b/packages/react-router-cloudflare/CHANGELOG.md
@@ -1,5 +1,12 @@
# `@react-router/cloudflare`
+## 7.10.1
+
+### Patch Changes
+
+- Updated dependencies:
+ - `react-router@7.10.1`
+
## 7.10.0
### Patch Changes
diff --git a/packages/react-router-cloudflare/package.json b/packages/react-router-cloudflare/package.json
index c1161a604b..41e8b707ed 100644
--- a/packages/react-router-cloudflare/package.json
+++ b/packages/react-router-cloudflare/package.json
@@ -1,6 +1,6 @@
{
"name": "@react-router/cloudflare",
- "version": "7.10.0",
+ "version": "7.10.1",
"description": "Cloudflare platform abstractions for React Router",
"bugs": {
"url": "https://github.com/remix-run/react-router/issues"
diff --git a/packages/react-router-dev/CHANGELOG.md b/packages/react-router-dev/CHANGELOG.md
index ffc9c3f957..b99d47b8e6 100644
--- a/packages/react-router-dev/CHANGELOG.md
+++ b/packages/react-router-dev/CHANGELOG.md
@@ -1,5 +1,16 @@
# `@react-router/dev`
+## 7.10.1
+
+### Patch Changes
+
+- Import ESM package `pkg-types` with a dynamic `import()` to fix issues on Node 20.18 ([#14624](https://github.com/remix-run/react-router/pull/14624))
+- Update `valibot` dependency to `^1.2.0` to address [GHSA-vqpr-j7v3-hqw9](https://github.com/advisories/GHSA-vqpr-j7v3-hqw9) ([#14608](https://github.com/remix-run/react-router/pull/14608))
+- Updated dependencies:
+ - `react-router@7.10.1`
+ - `@react-router/node@7.10.1`
+ - `@react-router/serve@7.10.1`
+
## 7.10.0
### Minor Changes
diff --git a/packages/react-router-dev/cli/commands.ts b/packages/react-router-dev/cli/commands.ts
index 42c3d604f1..3e4d615932 100644
--- a/packages/react-router-dev/cli/commands.ts
+++ b/packages/react-router-dev/cli/commands.ts
@@ -3,7 +3,6 @@ import { readFile, writeFile } from "node:fs/promises";
import * as path from "node:path";
import exitHook from "exit-hook";
import colors from "picocolors";
-import { readPackageJSON } from "pkg-types";
// Workaround for "ERR_REQUIRE_CYCLE_MODULE" in Node 22.10.0+
import "react-router";
@@ -138,6 +137,8 @@ export async function generateEntry(
return;
}
+ // TODO(v8): Remove - only required for Node 20.18 and below
+ let { readPackageJSON } = await import("pkg-types");
let pkgJson = await readPackageJSON(rootDirectory);
let deps = pkgJson.dependencies ?? {};
diff --git a/packages/react-router-dev/config/config.ts b/packages/react-router-dev/config/config.ts
index 9edaa01175..e69c89960f 100644
--- a/packages/react-router-dev/config/config.ts
+++ b/packages/react-router-dev/config/config.ts
@@ -8,7 +8,6 @@ import chokidar, {
type EmitArgs as ChokidarEmitArgs,
} from "chokidar";
import colors from "picocolors";
-import { readPackageJSON, sortPackage, updatePackage } from "pkg-types";
import pick from "lodash/pick";
import omit from "lodash/omit";
import cloneDeep from "lodash/cloneDeep";
@@ -933,6 +932,10 @@ export async function resolveEntryFiles({
);
}
+ // TODO(v8): Remove - only required for Node 20.18 and below
+ let { readPackageJSON, sortPackage, updatePackage } = await import(
+ "pkg-types"
+ );
let packageJsonDirectory = Path.dirname(packageJsonPath);
let pkgJson = await readPackageJSON(packageJsonDirectory);
let deps = pkgJson.dependencies ?? {};
diff --git a/packages/react-router-dev/package.json b/packages/react-router-dev/package.json
index 89ea0e5ff8..257e5564a9 100644
--- a/packages/react-router-dev/package.json
+++ b/packages/react-router-dev/package.json
@@ -1,6 +1,6 @@
{
"name": "@react-router/dev",
- "version": "7.10.0",
+ "version": "7.10.1",
"description": "Dev tools and CLI for React Router",
"homepage": "https://reactrouter.com",
"bugs": {
@@ -90,7 +90,7 @@
"react-refresh": "^0.14.0",
"semver": "^7.3.7",
"tinyglobby": "^0.2.14",
- "valibot": "^1.1.0",
+ "valibot": "^1.2.0",
"vite-node": "^3.2.2"
},
"devDependencies": {
diff --git a/packages/react-router-dom/CHANGELOG.md b/packages/react-router-dom/CHANGELOG.md
index 156553ae65..96e5671f9c 100644
--- a/packages/react-router-dom/CHANGELOG.md
+++ b/packages/react-router-dom/CHANGELOG.md
@@ -1,5 +1,12 @@
# react-router-dom
+## 7.10.1
+
+### Patch Changes
+
+- Updated dependencies:
+ - `react-router@7.10.1`
+
## 7.10.0
### Patch Changes
diff --git a/packages/react-router-dom/package.json b/packages/react-router-dom/package.json
index 0c460545e4..727dfb361c 100644
--- a/packages/react-router-dom/package.json
+++ b/packages/react-router-dom/package.json
@@ -1,6 +1,6 @@
{
"name": "react-router-dom",
- "version": "7.10.0",
+ "version": "7.10.1",
"description": "Declarative routing for React web applications",
"keywords": [
"react",
diff --git a/packages/react-router-express/CHANGELOG.md b/packages/react-router-express/CHANGELOG.md
index 7de371d64c..0902d58375 100644
--- a/packages/react-router-express/CHANGELOG.md
+++ b/packages/react-router-express/CHANGELOG.md
@@ -1,5 +1,13 @@
# `@react-router/express`
+## 7.10.1
+
+### Patch Changes
+
+- Updated dependencies:
+ - `react-router@7.10.1`
+ - `@react-router/node@7.10.1`
+
## 7.10.0
### Patch Changes
diff --git a/packages/react-router-express/package.json b/packages/react-router-express/package.json
index ed75dbe6e7..3ad6f94aaf 100644
--- a/packages/react-router-express/package.json
+++ b/packages/react-router-express/package.json
@@ -1,6 +1,6 @@
{
"name": "@react-router/express",
- "version": "7.10.0",
+ "version": "7.10.1",
"description": "Express server request handler for React Router",
"bugs": {
"url": "https://github.com/remix-run/react-router/issues"
diff --git a/packages/react-router-fs-routes/CHANGELOG.md b/packages/react-router-fs-routes/CHANGELOG.md
index 5a11b3b4ce..319705480e 100644
--- a/packages/react-router-fs-routes/CHANGELOG.md
+++ b/packages/react-router-fs-routes/CHANGELOG.md
@@ -1,5 +1,12 @@
# `@react-router/fs-routes`
+## 7.10.1
+
+### Patch Changes
+
+- Updated dependencies:
+ - `@react-router/dev@7.10.1`
+
## 7.10.0
### Patch Changes
diff --git a/packages/react-router-fs-routes/package.json b/packages/react-router-fs-routes/package.json
index ede8aa593d..479a5fba0e 100644
--- a/packages/react-router-fs-routes/package.json
+++ b/packages/react-router-fs-routes/package.json
@@ -1,6 +1,6 @@
{
"name": "@react-router/fs-routes",
- "version": "7.10.0",
+ "version": "7.10.1",
"description": "File system routing conventions for React Router, for use within routes.ts",
"bugs": {
"url": "https://github.com/remix-run/react-router/issues"
diff --git a/packages/react-router-node/CHANGELOG.md b/packages/react-router-node/CHANGELOG.md
index a36b0812a4..ff7d06d6dc 100644
--- a/packages/react-router-node/CHANGELOG.md
+++ b/packages/react-router-node/CHANGELOG.md
@@ -1,5 +1,12 @@
# `@react-router/node`
+## 7.10.1
+
+### Patch Changes
+
+- Updated dependencies:
+ - `react-router@7.10.1`
+
## 7.10.0
### Patch Changes
diff --git a/packages/react-router-node/package.json b/packages/react-router-node/package.json
index 677c6e4fe3..8a9c08565e 100644
--- a/packages/react-router-node/package.json
+++ b/packages/react-router-node/package.json
@@ -1,6 +1,6 @@
{
"name": "@react-router/node",
- "version": "7.10.0",
+ "version": "7.10.1",
"description": "Node.js platform abstractions for React Router",
"bugs": {
"url": "https://github.com/remix-run/react-router/issues"
diff --git a/packages/react-router-remix-routes-option-adapter/CHANGELOG.md b/packages/react-router-remix-routes-option-adapter/CHANGELOG.md
index 141c35dd7f..951495cd51 100644
--- a/packages/react-router-remix-routes-option-adapter/CHANGELOG.md
+++ b/packages/react-router-remix-routes-option-adapter/CHANGELOG.md
@@ -1,5 +1,12 @@
# `@react-router/remix-config-routes-adapter`
+## 7.10.1
+
+### Patch Changes
+
+- Updated dependencies:
+ - `@react-router/dev@7.10.1`
+
## 7.10.0
### Patch Changes
diff --git a/packages/react-router-remix-routes-option-adapter/package.json b/packages/react-router-remix-routes-option-adapter/package.json
index da90307840..81bcc81091 100644
--- a/packages/react-router-remix-routes-option-adapter/package.json
+++ b/packages/react-router-remix-routes-option-adapter/package.json
@@ -1,6 +1,6 @@
{
"name": "@react-router/remix-routes-option-adapter",
- "version": "7.10.0",
+ "version": "7.10.1",
"description": "Adapter for Remix's \"routes\" config option, for use within routes.ts",
"bugs": {
"url": "https://github.com/remix-run/react-router/issues"
diff --git a/packages/react-router-serve/CHANGELOG.md b/packages/react-router-serve/CHANGELOG.md
index d615046e77..7b3f260d23 100644
--- a/packages/react-router-serve/CHANGELOG.md
+++ b/packages/react-router-serve/CHANGELOG.md
@@ -1,5 +1,14 @@
# `@react-router/serve`
+## 7.10.1
+
+### Patch Changes
+
+- Updated dependencies:
+ - `react-router@7.10.1`
+ - `@react-router/node@7.10.1`
+ - `@react-router/express@7.10.1`
+
## 7.10.0
### Patch Changes
diff --git a/packages/react-router-serve/package.json b/packages/react-router-serve/package.json
index 859abb7111..2e1cc65520 100644
--- a/packages/react-router-serve/package.json
+++ b/packages/react-router-serve/package.json
@@ -1,6 +1,6 @@
{
"name": "@react-router/serve",
- "version": "7.10.0",
+ "version": "7.10.1",
"description": "Production application server for React Router",
"bugs": {
"url": "https://github.com/remix-run/react-router/issues"
diff --git a/packages/react-router/CHANGELOG.md b/packages/react-router/CHANGELOG.md
index dfe7f01353..39d50f8991 100644
--- a/packages/react-router/CHANGELOG.md
+++ b/packages/react-router/CHANGELOG.md
@@ -1,5 +1,11 @@
# `react-router`
+## 7.10.1
+
+### Patch Changes
+
+- Update the `useOptimistic` stub we provide for React 18 users to use a stable setter function to avoid potential `useEffect` loops - specifically when using `` ([#14628](https://github.com/remix-run/react-router/pull/14628))
+
## 7.10.0
### Minor Changes
diff --git a/packages/react-router/lib/components.tsx b/packages/react-router/lib/components.tsx
index 8ed10893e8..64e6376751 100644
--- a/packages/react-router/lib/components.tsx
+++ b/packages/react-router/lib/components.tsx
@@ -77,8 +77,8 @@ import { warnOnce } from "./server-runtime/warnings";
import type { unstable_ClientInstrumentation } from "./router/instrumentation";
/**
- * Webpack can fail to compile on against react versions without this export
- * complains that `startTransition` doesn't exist in `React`.
+ * Webpack can fail to compile against react versions without this export -
+ * it complains that `useOptimistic` doesn't exist in `React`.
*
* Using the string constant directly at runtime fixes the webpack build issue
* but can result in terser stripping the actual call at minification time.
@@ -90,6 +90,7 @@ import type { unstable_ClientInstrumentation } from "./router/instrumentation";
const USE_OPTIMISTIC = "useOptimistic";
// @ts-expect-error Needs React 19 types but we develop against 18
const useOptimisticImpl = React[USE_OPTIMISTIC];
+const stableUseOptimisticSetter = () => undefined;
function useOptimisticSafe(
val: T,
@@ -98,7 +99,7 @@ function useOptimisticSafe(
// eslint-disable-next-line react-hooks/rules-of-hooks
return useOptimisticImpl(val);
} else {
- return [val, () => undefined];
+ return [val, stableUseOptimisticSetter];
}
}
diff --git a/packages/react-router/lib/dom/lib.tsx b/packages/react-router/lib/dom/lib.tsx
index 6d192efaf7..d740bf6c06 100644
--- a/packages/react-router/lib/dom/lib.tsx
+++ b/packages/react-router/lib/dom/lib.tsx
@@ -1038,16 +1038,16 @@ HistoryRouter.displayName = "unstable_HistoryRouter";
export interface LinkProps
extends Omit, "href"> {
/**
- * Defines the link discovery behavior
+ * Defines the link [lazy route discovery](../../explanation/lazy-route-discovery) behavior.
+ *
+ * - **render** — default, discover the route when the link renders
+ * - **none** — don't eagerly discover, only discover if the link is clicked
*
* ```tsx
* // default ("render")
*
*
* ```
- *
- * - **render** — default, discover the route when the link renders
- * - **none** — don't eagerly discover, only discover if the link is clicked
*/
discover?: DiscoverBehavior;
@@ -1717,16 +1717,16 @@ export interface FetcherFormProps extends SharedFormProps {}
*/
export interface FormProps extends SharedFormProps {
/**
- * Defines the link discovery behavior. See {@link DiscoverBehavior}.
+ * Defines the form [lazy route discovery](../../explanation/lazy-route-discovery) behavior.
+ *
+ * - **render** — default, discover the route when the form renders
+ * - **none** — don't eagerly discover, only discover if the form is submitted
*
* ```tsx
- * // default ("render")
- *
- *
+ * // default ("render")
+ *
+ *
* ```
- *
- * - **render** — default, discover the route when the link renders
- * - **none** — don't eagerly discover, only discover if the link is clicked
*/
discover?: DiscoverBehavior;
diff --git a/packages/react-router/lib/dom/ssr/components.tsx b/packages/react-router/lib/dom/ssr/components.tsx
index 318fa0bf51..57edd93007 100644
--- a/packages/react-router/lib/dom/ssr/components.tsx
+++ b/packages/react-router/lib/dom/ssr/components.tsx
@@ -76,7 +76,8 @@ export function useFrameworkContext(): FrameworkContextObject {
// Public API
/**
- * Defines the discovery behavior of the link:
+ * Defines the [lazy route discovery](../../explanation/lazy-route-discovery)
+ * behavior of the link/form:
*
* - "render" - default, discover the route when the link renders
* - "none" - don't eagerly discover, only discover if the link is clicked
diff --git a/packages/react-router/package.json b/packages/react-router/package.json
index 29b8e5cc80..944f04dffa 100644
--- a/packages/react-router/package.json
+++ b/packages/react-router/package.json
@@ -1,6 +1,6 @@
{
"name": "react-router",
- "version": "7.10.0",
+ "version": "7.10.1",
"description": "Declarative routing for React",
"keywords": [
"react",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index dd0281e157..af765b3984 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1120,8 +1120,8 @@ importers:
specifier: ^0.2.14
version: 0.2.14
valibot:
- specifier: ^1.1.0
- version: 1.1.0(typescript@5.4.5)
+ specifier: ^1.2.0
+ version: 1.2.0(typescript@5.4.5)
vite-node:
specifier: ^3.2.2
version: 3.2.4(@types/node@20.11.30)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0)
@@ -9507,8 +9507,8 @@ packages:
typescript:
optional: true
- valibot@1.1.0:
- resolution: {integrity: sha512-Nk8lX30Qhu+9txPYTwM0cFlWLdPFsFr6LblzqIySfbZph9+BFsAHsNvHOymEviUepeIW6KFHzpX8TKhbptBXXw==}
+ valibot@1.2.0:
+ resolution: {integrity: sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==}
peerDependencies:
typescript: '>=5'
peerDependenciesMeta:
@@ -19198,7 +19198,7 @@ snapshots:
optionalDependencies:
typescript: 5.4.5
- valibot@1.1.0(typescript@5.4.5):
+ valibot@1.2.0(typescript@5.4.5):
optionalDependencies:
typescript: 5.4.5