Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
5 changes: 5 additions & 0 deletions .changeset/react-router-v8-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@lambdacurry/forms": minor
---

Support React Router v8. `react-router` is now a peer dependency (`^7.0.0 || ^8.0.0`) instead of a bundled dependency, so the library resolves the router your application installs (keep the Vite `resolve.dedupe` / `ssr.noExternal` setup from the consumer guide so only one runtime instance is bundled), and the unused `react-router-dom` dependency/peer was dropped (that package no longer exists in React Router v8). React Router v8 requires Node 22.22 or newer.
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,4 @@ Quick checklist
- Forms: Zod schemas, proper messages, `fetcher.Form`, show `FormMessage` errors.
- Tests: per-story decorators, semantic queries, three-phase play tests; run `yarn test`.
- Monorepo: no cross-package relative imports; verify `exports`, TS `paths`, Turbo outputs.
- Consumer integration: For React Router v7 setup help, reference `docs/consumer-setup-guide.md` for Vite SSR configuration (ssr.noExternal, optimizeDeps).
- Consumer integration: For React Router v7/v8 setup help, reference `docs/consumer-setup-guide.md` for Vite SSR configuration (ssr.noExternal, optimizeDeps).
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,11 @@ const MyTable = () => {
- Full accessibility support (WCAG 2.1 AA)
- Comprehensive test coverage

## React Router v7 Integration
## React Router v7 / v8 Integration

When using `@lambdacurry/forms` with `remix-hook-form` in a React Router v7 application, you need to configure Vite to bundle these packages together to share the router context. Without this, you may encounter the error:
`react-router` (v7 or v8) and `remix-hook-form` are peer dependencies: install them in your application so the library resolves the router your app provides. React Router v8 itself requires Node 22.22 or newer in the consuming application.

When using `@lambdacurry/forms` with `remix-hook-form` in a React Router v7 or v8 application, you need to configure Vite to bundle these packages together to share the router context. Without this, you may encounter the error:

```
Error: useHref() may be used only in the context of a <Router> component.
Expand All @@ -82,9 +84,11 @@ export default defineConfig({
ssr: {
noExternal: ['react-hook-form', 'remix-hook-form', '@lambdacurry/forms']
},
optimizeDeps: {
include: ['react', 'react-dom', 'react-router', 'react-hook-form', 'remix-hook-form'],
resolve: {
dedupe: ['react', 'react-dom', 'react-router', 'react-hook-form', 'remix-hook-form']
},
optimizeDeps: {
include: ['react', 'react-dom', 'react-router', 'react-hook-form', 'remix-hook-form']
}
});
```
Expand Down
18 changes: 11 additions & 7 deletions docs/consumer-setup-guide.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# Consumer Setup Guide

This guide covers how to integrate `@lambdacurry/forms` with React Router v7 applications using remix-hook-form.
This guide covers how to integrate `@lambdacurry/forms` with React Router v7 or v8 applications using remix-hook-form.

## React Router v7 Vite Configuration
`react-router` (`^7 || ^8`) and `remix-hook-form` are peer dependencies of `@lambdacurry/forms`; install them in your application. The package no longer references `react-router-dom`, which was removed in React Router v8. React Router v8 requires Node 22.22 or newer.

When using `@lambdacurry/forms` with `remix-hook-form` in a React Router v7 application, you must configure Vite to bundle these packages together. Without this configuration, forms that render conditionally (e.g., triggered by a button click) will fail with:
## React Router Vite Configuration

When using `@lambdacurry/forms` with `remix-hook-form` in a React Router v7 or v8 application, you must configure Vite to bundle these packages together. Without this configuration, forms that render conditionally (e.g., triggered by a button click) will fail with:

```
Error: useHref() may be used only in the context of a <Router> component.
Expand Down Expand Up @@ -32,11 +34,13 @@ export default defineConfig({
// CRITICAL: Bundle these packages with the app to share react-router context
noExternal: ['react-hook-form', 'remix-hook-form', '@lambdacurry/forms']
},
optimizeDeps: {
// Pre-bundle dependencies to avoid runtime context issues
include: ['react', 'react-dom', 'react-router', 'react-hook-form', 'remix-hook-form'],
resolve: {
// Ensure single instances of these packages
dedupe: ['react', 'react-dom', 'react-router', 'react-hook-form', 'remix-hook-form']
},
optimizeDeps: {
// Pre-bundle dependencies to avoid runtime context issues
include: ['react', 'react-dom', 'react-router', 'react-hook-form', 'remix-hook-form']
}
});
```
Expand All @@ -47,7 +51,7 @@ export default defineConfig({
|---------|---------|
| `ssr.noExternal` | Forces Vite to bundle `remix-hook-form`, `react-hook-form`, and `@lambdacurry/forms` with the application instead of treating them as external dependencies. This ensures they share the same `react-router` instance. |
| `optimizeDeps.include` | Pre-bundles these packages during dev, avoiding lazy loading that can cause context issues. |
| `optimizeDeps.dedupe` | Ensures only one copy of each package exists, preventing multiple React or react-router instances. |
| `resolve.dedupe` | Ensures only one copy of each package exists, preventing multiple React or react-router instances. |

## Recommended Form Pattern

Expand Down
6 changes: 2 additions & 4 deletions packages/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,7 @@
},
"peerDependencies": {
"react": "^19.0.0",
"react-router": "^7.0.0",
"react-router-dom": "^7.0.0",
"react-router": "^7.0.0 || ^8.0.0",
Comment thread
coderabbitai[bot] marked this conversation as resolved.
"remix-hook-form": "7.1.0",
"zod": "^3.24.1 || ^4.0.0"
},
Expand Down Expand Up @@ -76,8 +75,6 @@
"next-themes": "^0.4.4",
"react-day-picker": "^9.7.0",
"react-hook-form": "^7.53.1",
"react-router": "^7.6.3",
"react-router-dom": "^7.6.3",
"remix-hook-form": "7.1.0",
"sonner": "^1.7.1",
"tailwind-merge": "^2.5.5",
Expand All @@ -94,6 +91,7 @@
"autoprefixer": "^10.4.20",
"glob": "^11.0.0",
"react": "^19.0.0",
"react-router": "^7.6.3",
"tailwindcss": "^4.0.0",
"typescript": "^5.7.2",
"vite": "^6.2.2",
Expand Down
1 change: 0 additions & 1 deletion packages/components/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ export default defineConfig({
'@radix-ui/react-switch',
'@radix-ui/react-tooltip',
'react-router',
'react-router-dom',
'@react-router/node',
'class-variance-authority',
'clsx',
Expand Down
21 changes: 10 additions & 11 deletions scripts/release-if-needed.mjs
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import { execFileSync } from "node:child_process";
import { readFileSync } from "node:fs";
import { execFileSync } from 'node:child_process';
import { readFileSync } from 'node:fs';

const publishablePackages = ["packages/components/package.json"];
const publishablePackages = ['packages/components/package.json'];

const unpublishedPackages = publishablePackages.filter((packagePath) => {
const localPackage = JSON.parse(readFileSync(packagePath, "utf8"));
const localPackage = JSON.parse(readFileSync(packagePath, 'utf8'));

try {
const exactVersion = execFileSync(
"npm",
["view", `${localPackage.name}@${localPackage.version}`, "version"],
{ encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] },
).trim();
const exactVersion = execFileSync('npm', ['view', `${localPackage.name}@${localPackage.version}`, 'version'], {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'ignore'],
}).trim();

return exactVersion !== localPackage.version;
} catch {
Expand All @@ -20,8 +19,8 @@ const unpublishedPackages = publishablePackages.filter((packagePath) => {
});

if (unpublishedPackages.length === 0) {
console.log("All publishable package versions are already on npm.");
console.log('All publishable package versions are already on npm.');
process.exit(0);
}

execFileSync("yarn", ["changeset", "publish"], { stdio: "inherit" });
execFileSync('yarn', ['changeset', 'publish'], { stdio: 'inherit' });
6 changes: 2 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1754,7 +1754,6 @@ __metadata:
react-day-picker: "npm:^9.7.0"
react-hook-form: "npm:^7.53.1"
react-router: "npm:^7.6.3"
react-router-dom: "npm:^7.6.3"
remix-hook-form: "npm:7.1.0"
sonner: "npm:^1.7.1"
tailwind-merge: "npm:^2.5.5"
Expand All @@ -1767,8 +1766,7 @@ __metadata:
zod: "npm:^3.24.1"
peerDependencies:
react: ^19.0.0
react-router: ^7.0.0
react-router-dom: ^7.0.0
react-router: ^7.0.0 || ^8.0.0
remix-hook-form: 7.1.0
zod: ^3.24.1 || ^4.0.0
languageName: unknown
Expand Down Expand Up @@ -10410,7 +10408,7 @@ __metadata:
languageName: node
linkType: hard

"react-router-dom@npm:^7.6.2, react-router-dom@npm:^7.6.3":
"react-router-dom@npm:^7.6.2":
version: 7.9.1
resolution: "react-router-dom@npm:7.9.1"
dependencies:
Expand Down
Loading