Skip to content

Commit 33ac356

Browse files
committed
docs: update consumer setup instructions for React Router v7 integration
- Added detailed Vite configuration requirements for using @lambdacurry/forms with remix-hook-form in React Router v7 applications to prevent routing errors. - Included references to the Consumer Setup Guide for comprehensive integration patterns. - Updated AGENTS.md and tech-stack.md to reflect these changes.
1 parent f0be95a commit 33ac356

File tree

5 files changed

+227
-0
lines changed

5 files changed

+227
-0
lines changed

.agentos/standards/tech-stack.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ Build, Dev, and Distribution
3434
- apps/docs uses Storybook (React + Vite) for docs and interaction tests
3535
- packages/components builds to dist/ (do not import from dist inside repo packages)
3636

37+
Consumer SSR/Bundling Requirements
38+
When consumers use @lambdacurry/forms in React Router v7 applications, they must configure Vite to bundle form packages together:
39+
- `ssr.noExternal`: Include react-hook-form, remix-hook-form, @lambdacurry/forms
40+
- `optimizeDeps.dedupe`: Include react, react-dom, react-router, react-hook-form, remix-hook-form
41+
- This prevents the "useHref() may be used only in the context of a <Router>" error
42+
- See /docs/consumer-setup-guide.md for complete configuration
43+
3744
Quality: Linting, Formatting, and Testing
3845
- Lint/format: Biome
3946
- 2-space indentation; max line width 120; single quotes; organized imports

.cursor/rules/form-component-patterns.mdc

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,4 +207,51 @@ export type { ComponentNameProps };
207207
- Verify server-side validation integration
208208
- Test component composition and customization
209209

210+
## Consumer Application Setup
211+
212+
When consumers use `@lambdacurry/forms` with `remix-hook-form` in a React Router v7 application, they must configure Vite to bundle packages together. Without this, forms may fail with:
213+
```
214+
Error: useHref() may be used only in the context of a <Router> component.
215+
```
216+
217+
### Required Vite Configuration
218+
```typescript
219+
// Consumer's vite.config.ts
220+
export default defineConfig({
221+
ssr: {
222+
// Bundle these packages to share react-router context
223+
noExternal: ['react-hook-form', 'remix-hook-form', '@lambdacurry/forms']
224+
},
225+
optimizeDeps: {
226+
include: ['react', 'react-dom', 'react-router', 'react-hook-form', 'remix-hook-form'],
227+
dedupe: ['react', 'react-dom', 'react-router', 'react-hook-form', 'remix-hook-form']
228+
}
229+
});
230+
```
231+
232+
### Why This Is Needed
233+
- `remix-hook-form`'s `useRemixForm` hook calls `useHref("/")` internally
234+
- Without `ssr.noExternal`, Vite treats these as external dependencies
235+
- This causes packages to load with a separate `react-router` instance
236+
- The hook then fails because it can't find the router context
237+
238+
### Form Submission with createFormData
239+
When using `submitHandlers.onValid`, always use `createFormData()`:
240+
```typescript
241+
const methods = useRemixForm<FormData>({
242+
resolver: zodResolver(schema),
243+
fetcher,
244+
submitHandlers: {
245+
onValid: (data) => {
246+
fetcher.submit(createFormData(data), {
247+
method: 'post',
248+
action: '/api/endpoint',
249+
});
250+
},
251+
},
252+
});
253+
```
254+
255+
See `/docs/consumer-setup-guide.md` for complete integration patterns.
256+
210257
Remember: Form components are the core of this library. Every form component should be intuitive, accessible, and integrate seamlessly with the Remix Hook Form + Zod validation pattern.

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,4 @@ Quick checklist
8181
- Forms: Zod schemas, proper messages, `fetcher.Form`, show `FormMessage` errors.
8282
- Tests: per-story decorators, semantic queries, three-phase play tests; run `yarn test`.
8383
- Monorepo: no cross-package relative imports; verify `exports`, TS `paths`, Turbo outputs.
84+
- Consumer integration: For React Router v7 setup help, reference `docs/consumer-setup-guide.md` for Vite SSR configuration (ssr.noExternal, optimizeDeps).

README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,31 @@ const MyTable = () => {
6666
- Full accessibility support (WCAG 2.1 AA)
6767
- Comprehensive test coverage
6868

69+
## React Router v7 Integration
70+
71+
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:
72+
73+
```
74+
Error: useHref() may be used only in the context of a <Router> component.
75+
```
76+
77+
Add the following to your application's `vite.config.ts`:
78+
79+
```typescript
80+
export default defineConfig({
81+
// ... your other plugins
82+
ssr: {
83+
noExternal: ['react-hook-form', 'remix-hook-form', '@lambdacurry/forms']
84+
},
85+
optimizeDeps: {
86+
include: ['react', 'react-dom', 'react-router', 'react-hook-form', 'remix-hook-form'],
87+
dedupe: ['react', 'react-dom', 'react-router', 'react-hook-form', 'remix-hook-form']
88+
}
89+
});
90+
```
91+
92+
This ensures all packages share the same `react-router` instance. See the [Consumer Setup Guide](./docs/consumer-setup-guide.md) for complete details and recommended form patterns.
93+
6994
## Getting Started
7095

7196
Step 1: Install dependencies

docs/consumer-setup-guide.md

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
# Consumer Setup Guide
2+
3+
This guide covers how to integrate `@lambdacurry/forms` with React Router v7 applications using remix-hook-form.
4+
5+
## React Router v7 Vite Configuration
6+
7+
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:
8+
9+
```
10+
Error: useHref() may be used only in the context of a <Router> component.
11+
```
12+
13+
### Why This Happens
14+
15+
`remix-hook-form`'s `useRemixForm` hook internally calls `useHref("/")`. When Vite's SSR bundling doesn't properly handle `remix-hook-form` and `react-hook-form`, these packages load with a separate instance of `react-router` that doesn't share the router context with your application.
16+
17+
This causes `useHref()` to fail because the hook is looking for router context in the wrong React tree.
18+
19+
### Required Vite Configuration
20+
21+
Add these settings to your application's `vite.config.ts`:
22+
23+
```typescript
24+
import { reactRouter } from '@react-router/dev/vite';
25+
import tailwindcss from '@tailwindcss/vite';
26+
import { defineConfig } from 'vite';
27+
import tsconfigPaths from 'vite-tsconfig-paths';
28+
29+
export default defineConfig({
30+
plugins: [reactRouter(), tsconfigPaths(), tailwindcss()],
31+
ssr: {
32+
// CRITICAL: Bundle these packages with the app to share react-router context
33+
noExternal: ['react-hook-form', 'remix-hook-form', '@lambdacurry/forms']
34+
},
35+
optimizeDeps: {
36+
// Pre-bundle dependencies to avoid runtime context issues
37+
include: ['react', 'react-dom', 'react-router', 'react-hook-form', 'remix-hook-form'],
38+
// Ensure single instances of these packages
39+
dedupe: ['react', 'react-dom', 'react-router', 'react-hook-form', 'remix-hook-form']
40+
}
41+
});
42+
```
43+
44+
### Configuration Breakdown
45+
46+
| Setting | Purpose |
47+
|---------|---------|
48+
| `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. |
49+
| `optimizeDeps.include` | Pre-bundles these packages during dev, avoiding lazy loading that can cause context issues. |
50+
| `optimizeDeps.dedupe` | Ensures only one copy of each package exists, preventing multiple React or react-router instances. |
51+
52+
## Recommended Form Pattern
53+
54+
Here's the recommended pattern for using `@lambdacurry/forms` with `remix-hook-form`:
55+
56+
```typescript
57+
import { zodResolver } from '@hookform/resolvers/zod';
58+
import { FormError, HiddenField, Textarea } from '@lambdacurry/forms';
59+
import { useEffect } from 'react';
60+
import { useFetcher, useRevalidator } from 'react-router';
61+
import { createFormData, RemixFormProvider, useRemixForm } from 'remix-hook-form';
62+
import { z } from 'zod';
63+
64+
const schema = z.object({
65+
text: z.string().min(1, 'Required'),
66+
author: z.string().default('User'),
67+
});
68+
69+
type FormData = z.infer<typeof schema>;
70+
71+
function MyForm({ onSuccess }: { onSuccess: () => void }) {
72+
const fetcher = useFetcher<{ success?: boolean; errors?: Record<string, { message: string }> }>();
73+
const revalidator = useRevalidator();
74+
75+
const methods = useRemixForm<FormData>({
76+
resolver: zodResolver(schema),
77+
defaultValues: { text: '', author: 'User' },
78+
fetcher,
79+
submitHandlers: {
80+
onValid: (data) => {
81+
// IMPORTANT: Use createFormData() to properly serialize
82+
fetcher.submit(createFormData(data), {
83+
method: 'post',
84+
action: '/api/endpoint',
85+
});
86+
},
87+
},
88+
});
89+
90+
useEffect(() => {
91+
if (fetcher.state === 'idle' && fetcher.data?.success) {
92+
revalidator.revalidate();
93+
methods.reset();
94+
onSuccess();
95+
}
96+
}, [fetcher.state, fetcher.data, revalidator, onSuccess, methods]);
97+
98+
return (
99+
<RemixFormProvider {...methods}>
100+
<form onSubmit={methods.handleSubmit}>
101+
<Textarea name="text" placeholder="Enter text..." rows={4} autoFocus />
102+
<HiddenField name="author" />
103+
<FormError className="mt-2" />
104+
<button type="submit" disabled={fetcher.state !== 'idle'}>
105+
{fetcher.state !== 'idle' ? 'Submitting...' : 'Submit'}
106+
</button>
107+
</form>
108+
</RemixFormProvider>
109+
);
110+
}
111+
```
112+
113+
### Key Points
114+
115+
1. **Use `createFormData()`** - When using `submitHandlers.onValid`, always use `createFormData()` from `remix-hook-form` to properly serialize form data.
116+
117+
2. **Use `useFetcher`** - For forms that don't navigate, use `useFetcher` instead of the standard form submission.
118+
119+
3. **Handle revalidation** - Call `revalidator.revalidate()` after successful submission to refresh data.
120+
121+
4. **Reset form state** - Call `methods.reset()` after successful submission to clear the form.
122+
123+
## Troubleshooting
124+
125+
### "useHref() may be used only in the context of a `<Router>` component"
126+
127+
**Cause**: Vite is treating `remix-hook-form` or `react-hook-form` as external dependencies, causing them to load with a separate `react-router` instance.
128+
129+
**Solution**: Add the `ssr.noExternal` and `optimizeDeps` configuration shown above.
130+
131+
### Form works on initial render but fails when opened dynamically
132+
133+
**Cause**: Same as above - the packages are being loaded lazily with a different router context.
134+
135+
**Solution**: Ensure all three packages (`react-hook-form`, `remix-hook-form`, `@lambdacurry/forms`) are listed in `ssr.noExternal`.
136+
137+
### Multiple React instances warning
138+
139+
**Cause**: Dependencies are being duplicated in the bundle.
140+
141+
**Solution**: Add `optimizeDeps.dedupe` with React and related packages.
142+
143+
## Related Documentation
144+
145+
- [Form Component Patterns](../packages/components/src/remix-hook-form/README.md)
146+
- [FormError Component Guide](./form-error-guide.md)
147+
- [remix-hook-form Documentation](https://github.com/Code-Forge-Net/remix-hook-form)

0 commit comments

Comments
 (0)