Skip to content
Open
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
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`, `react-router` (v7 or v8), `remix-hook-form` and `zod` are peer dependencies: install them in your application so the library resolves the router (and React) 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
22 changes: 13 additions & 9 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
`@lambdacurry/forms` declares four peer dependencies that your application must install: `react` (`^19.0.0`), `react-router` (`^7.0.0 || ^8.0.0`), `remix-hook-form` (`7.1.0`) and `zod` (`^3.24.1 || ^4.0.0`). 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 Expand Up @@ -126,7 +130,7 @@ function MyForm({ onSuccess }: { onSuccess: () => void }) {

**Cause**: Vite is treating `remix-hook-form` or `react-hook-form` as external dependencies, causing them to load with a separate `react-router` instance.

**Solution**: Add the `ssr.noExternal` and `optimizeDeps` configuration shown above.
**Solution**: Add the `ssr.noExternal`, `resolve.dedupe` and `optimizeDeps.include` configuration shown above.

### Form works on initial render but fails when opened dynamically

Expand All @@ -138,7 +142,7 @@ function MyForm({ onSuccess }: { onSuccess: () => void }) {

**Cause**: Dependencies are being duplicated in the bundle.

**Solution**: Add `optimizeDeps.dedupe` with React and related packages.
**Solution**: Add `resolve.dedupe` with React and related packages, as in the configuration above.

## Related Documentation

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