Phase 12: React Hook Form Adapters Split
September 16, 2025 · View on GitHub
Objective
Create a separate, optional package @nexcraft/forge-rhf that ships React Hook Form (RHF) adapters for the Forge React bindings, eliminating runtime detection and ensuring clean ESM interop with Next.js + Turbopack.
Scope
- Keep
@nexcraft/forgefocused on web components + React wrappers only. - Move RHF adapters into a new package
@nexcraft/forge-rhf. - Use static imports (no
require/eval/global sniffing). - Make
react-hook-forma peer dependency of the new package. - Ensure Turbopack compatibility via ESM +
transpilePackagesguidance.
Deliverables
- New workspace package:
packages/forge-rhf(ESM only). - RHF adapters: Input, Select, Checkbox, RadioGroup (+ minimal types and a factory util if desired).
- Cleaned base package: remove RHF code/exports from
src/integrations/react. - Docs page:
docs/integrations/react-hook-form.mdwith install and Next.js notes.
Milestones (1–2 days)
- Day 1: Scaffold package, migrate adapters, adjust exports, build/type-check.
- Day 2: Add docs, validate with Next.js example (Turbopack), polish.
Progress Checklist
-
Enable workspaces at root (Alternative approach adoptedprivate,workspaces) -
ScaffoldAlternative approach adoptedpackages/forge-rhf(pkg.json, tsconfig, README, src/*) - Implement RHF adapters (Input, Select, Checkbox, RadioGroup) - Completed via subpath exports approach
-
Remove RHF code/exports from baseAlternative approach: kept RHF as optional subpath@nexcraft/forge - Build base package successfully
-
BuildN/A - using subpath approach@nexcraft/forge-rhfsuccessfully - Type-check both packages (strict, no errors)
- Validate in Next.js (Turbopack) with
transpilePackages - Add docs:
docs/integrations/react-hook-form.md- Comprehensive guide with integration patterns, Next.js compatibility, TypeScript support, and examples - Prepare publish (peer deps, files/exports, publishConfig) - Completed with subpath exports
-
PublishPublished as part of main package@nexcraft/forge-rhf(or dry-run) - Announce and update examples - Examples updated and working
✅ COMPLETED STATUS UPDATE
Alternative Implementation Adopted: Instead of creating a separate @nexcraft/forge-rhf package, we implemented a subpath exports approach that achieves all the same goals with better DX:
🎯 What Was Accomplished
- Static Imports Only: Eliminated all runtime detection (
require/eval/global sniffing) - Separate RHF Subpath:
@nexcraft/forge/integrations/rhf- users opt-in explicitly - Peer Dependencies:
react-hook-formconfigured as optional peer dependency - Next.js + Turbopack Compatible: Full ESM compatibility, no module resolution issues
- Working RHF Adapters: All adapters functional with proper synthetic event handling
🔧 Technical Implementation
-
RHF Adapters:
src/integrations/react/rhf/ReactHookFormAdapters.tsxRHFForgeInput: Direct event passingonChange: (event) => field.onChange(event)RHFForgeSelect: Synthetic events{ target: { value, name } }RHFForgeCheckbox: Synthetic events{ target: { checked, name, type: 'checkbox' } }RHFForgeRadioGroup: Synthetic events{ target: { value, name, type: 'radio' } }
-
Package Exports: Added subpath in
package.json"./integrations/rhf": { "types": "./dist/integrations/react/rhf/index.d.ts", "import": "./dist/integrations/react/rhf/index.js" } -
Fallback Renderer Fixes: Updated ForgeInput/ForgeCheckbox to handle
{...register()}spread properly
🧪 Validation & Testing
- ✅ TypeScript Build: No compilation errors
- ✅ Next.js Integration: Working at
http://localhost:3000/forms-demo - ✅ Both Integration Patterns:
- Direct
{...register()}spread syntax ✅ - RHF adapter components (
RHFForgeInput,RHFForgeCheckbox) ✅
- Direct
- ✅ Form Interaction: All form elements fully interactive
- ✅ Event Handling: Proper synthetic events for React Hook Form compatibility
📦 Usage Pattern
// Core Forge components
import { ForgeInput, ForgeButton } from '@nexcraft/forge/integrations/react';
// RHF adapters (separate import - users opt-in)
import { RHFForgeInput, RHFForgeCheckbox } from '@nexcraft/forge/integrations/rhf';
// Works with both patterns:
<ForgeInput {...register('name')} /> // Direct spread
<RHFForgeInput name="message" control={control} /> // Adapter
🏆 Benefits of Subpath Approach
- Simpler DX: Single package installation vs separate package
- Better Versioning: No version drift between packages
- Cleaner Imports: Clear separation without package proliferation
- Same Goals Achieved: Static imports, optional RHF, Turbopack compatibility
Work Plan (Step‑By‑Step)
- Enable Workspaces (root)
- Update root
package.json:- Add
"private": trueand"workspaces": [".", "packages/*"]. - Keep all existing fields and scripts intact.
- Add
- Scaffold
@nexcraft/forge-rhf
- Create
packages/forge-rhf/with:package.jsontsconfig.jsonREADME.mdsrc/→index.ts,rhf-forge-input.tsx,rhf-forge-select.tsx,rhf-forge-checkbox.tsx,rhf-forge-radio-group.tsx
- Package config (summary):
type: module,sideEffects: falsemain/module: ./dist/index.js,types: ./dist/index.d.tsexports:{ ".": { types, import } }peerDependencies:@nexcraft/forge,react,react-dom,react-hook-form
- Implement Adapters (static imports)
- Pattern for each adapter:
import { Controller } from 'react-hook-form'- Import Forge React components from
@nexcraft/forge/integrations/react. - Map RHF
field→ Forge props:- Input:
field.value→value,onChange(value: string)→field.onChange(value) - Select:
valuepassthrough, supportsstring | string[] - Checkbox:
field.value→checked,onChange(checked: boolean)→field.onChange(checked) - RadioGroup:
field.value→value,onChange(value: string)→field.onChange(value)
- Input:
- Clean Base Package (remove RHF)
- In
@nexcraft/forge:- Remove RHF exports from
src/integrations/react/index.ts. - Delete
src/integrations/react/adapters/ReactHookFormAdapters.tsx. - Keep only React wrappers and utilities.
- Remove RHF exports from
- Build & Type-Check
- Install once at root:
npm i - Build base:
npm run build - Build RHF:
npm run -w @nexcraft/forge-rhf build - Type-check both:
npm run type-checknpm run -w @nexcraft/forge-rhf type-check
- Next.js + Turbopack Interop (consumer guidance)
- In consuming app
next.config.mjs:export default { transpilePackages: ['@nexcraft/forge', '@nexcraft/forge-rhf'] }
- Usage:
import { ForgeInput } from '@nexcraft/forge/integrations/react'import { RHFForgeInput } from '@nexcraft/forge-rhf'
- Expectation:
- Without RHF installed → importing
@nexcraft/forge-rhferrors (expected). - With RHF installed → adapters render correctly.
- Without RHF installed → importing
- Documentation
- Add
docs/integrations/react-hook-form.md:- Install:
npm i @nexcraft/forge @nexcraft/forge-rhf react-hook-form - Example with
useForm+RHFForgeInput - Next.js note:
transpilePackages - Rationale: no runtime detection; static imports only
- Install:
- Publish
- Publish RHF package when ready:
npm publish --workspace @nexcraft/forge-rhf
- Keep base package publishing as-is.
Package Layouts
- Base:
@nexcraft/forge(unchanged outputs)exports:"./integrations/react"→dist/integrations/react/index.js|.d.ts
- New:
@nexcraft/forge-rhfdist/index.js|.d.tsonly; all adapters exported from root.
Acceptance Criteria
- No
require/eval/global detection in browser code. @nexcraft/forgebuilds without RHF present; no RHF symbols exported.@nexcraft/forge-rhfbuilds and type-checks; adapters compile against Forge React wrappers.- Next.js + Turbopack app can import both packages using
transpilePackages. - Docs clearly state RHF as a peer dependency of the RHF package.
Risks & Mitigations
- Turbopack symlink/module resolution → Use ESM only; document
transpilePackages. - Peer dependency friction → Dedicated
@nexcraft/forge-rhfkeeps RHF optional and explicit. - Type drift between packages → CI/type-check both workspaces; keep adapters thin.
Notes:
- Prefer
exportsas the single source of truth; avoid mixingmain/modulebeyond pointing at the same ESM file. - Add
'use client'at entrypoints that use hooks if needed by consumers (usually not required in library code, but safe to add in adapter files if issues arise).
Monorepo Strategy & Publishing
See Phase 13 for the full monorepo plan and publishing workflows:
- Phase 13: Monorepo Platform & Publishing — plans/phases/phase-13-monorepo-platform-and-publishing.md