Skip to content

fix(form): drop AntD Form/FormItem/useForm bridge, use native React state#7

Merged
Agions merged 1 commit into
mainfrom
fix/form-antd-api-compat
Jun 4, 2026
Merged

fix(form): drop AntD Form/FormItem/useForm bridge, use native React state#7
Agions merged 1 commit into
mainfrom
fix/form-antd-api-compat

Conversation

@Agions
Copy link
Copy Markdown
Owner

@Agions Agions commented Jun 4, 2026

🐱 fix(form): drop AntD Form/FormItem/useForm bridge, use native React state

Why

User direction: project doesn't need AntD compat, doesn't need AntD structural config.

The <Form>/<FormItem>/useForm shim in shared/components/ui/ui-components.tsx was fake AntD — neither real AntD nor real RHF, just a 97-line wrapper that bridged useRhfForm to AntD's setFieldsValue/validateFields/getFieldValue API. The Phase 2 dead-code cleanup (#6) exposed it via broken jest-skipped ProjectEdit/ProjectDetail tests that depended on this shim.

The shim had 4 call sites, and 3 of them never used the validation/form-library features at all — they only used form as a value holder for 2-12 primitive fields with no cross-field validation. Native useState is the simplest correct form library for these cases.

What changed

src/shared/components/ui/ui-components.tsx (-97 lines):

  • <Form> component (97 lines, fake AntD onFinish bridge)
  • <FormItem> component + FormWithItem compound pattern
  • useForm re-export (was useRhfForm as useForm)
  • FormValues type
  • ValidationRule/FormProps/FormItemProps interfaces
  • useRhfForm import
  • ✅ Added comment block explaining the new contract: use useState for simple forms, or import { useForm } from 'react-hook-form' directly for complex ones
  • ✅ Preserved non-Form re-exports (Modal/Spin/Space/Empty/Button/Card/Input/etc.) — 3 of 7 importers don't touch Form and need no changes

4 call sites converted to native React:

File Change
pages/project-edit/ProjectEditPage.tsx useForm()useState for name/description; form.setFieldsValuesetName(); form.validateFields()if (!name.trim()); form.getFieldValue('name')name; form.getFieldsValue() → inline literal. Page never rendered <Form> anyway.
features/script/components/ScriptGenerator.tsx 8 useState hooks; <Form onFinish={handleGenerate}> → native <form onSubmit>; 8 <FormItem><label> + controlled <Input>/<Select>/<RadioGroup>; form.handleSubmit(handleGenerate)() (2 sites) → void handleGenerate(); submit onClickhtmlType="submit"
shared/components/business/CompositionStudio/GlobalSettingsForm.tsx Was a dead shell (Slider/InputNumber/Select had no value/onChange) — now functional: 4 useState + onSave wire-up. Also adds onSave submit button.
shared/components/business/CompositionStudio/FrameEditForm.tsx Same dead-shell pattern, now 12 useState + save/reset buttons. CameraMotion | nullCameraMotion | undefined (Select doesn't accept null value).
shared/components/business/CompositionStudio/index.tsx 3 <FormItem label=> → plain <label> (skeleton-only, no state needed)

Verification

Check Result
npm run lint --quiet ✅ 0 errors
npx tsc --noEmit ✅ 0 errors
npm test --runInBand (90 suites) ✅ 1596 pass, 4 skip, 0 fail
npm run build ✅ OK

Bundle impact

  • react-vendor chunk: 311 KB → 287 KB (~24 KB smaller — react-hook-form no longer pulled into vendor chunk since no UI shim re-exports it)
  • ui-vendor chunk: unchanged (118 KB)

Diff

 6 files changed, 361 insertions(+), 400 deletions(-)

Out of scope (intentionally NOT touched)

  • The __tests__/pages/project-{edit,detail}.test.tsx jest-skip entries stay in place. Now that the form refactor is done, the underlying useState is honest, but the ProjectEditPage still needs an <Input> UI element to be user-editable. That's a separate feature (real project metadata form), not a refactor. Tests can be re-enabled when that UI lands.
  • Modal/Spin/Space/Empty/Button/Card/Input re-exports stay — they're AntD-style names but shadcn/RHF implementations, not part of the Form/FormItem bridge.
  • The FormWithItem as Form export for non-Form usage is gone too (was unused in the codebase).

Lesson learned (for future agent runs)

The fake-AntD shim is a textbook example of "in-progress refactor" residue: someone started bridging RHF to AntD-style API, shipped 97 lines of bridging code, then 4 call sites used it inconsistently and never finished the migration. Both Phase 1 (back-compat shims) and this form refactor cleanup the same kind of dead scaffolding. If you find yourself reaching for an <Form>/<FormItem> API, ask first: does this need a form library, or is useState enough?

…nstead

User direction: project doesn't need AntD compat, doesn't need AntD
structural config. The <Form>/<FormItem>/useForm shim in
shared/components/ui/ui-components.tsx was fake AntD (neither real AntD
nor real RHF) and a source of confusion — Phase 2 dead-code cleanup
exposed it via broken jest-skipped ProjectEdit/ProjectDetail tests that
depended on this shim.

Removed from ui-components.tsx:
  - <Form> component (97 lines, fake AntD onFinish bridge)
  - <FormItem> component + FormWithItem compound pattern
  - useForm re-export (was useRhfForm as useForm)
  - FormValues type
  - ValidationRule/FormProps/FormItemProps interfaces
  - useRhfForm import

Replaced in 4 call sites with native React:

1. pages/project-edit/ProjectEditPage.tsx
   - useForm() bridge → useState for 2 fields (name, description)
   - setFieldsValue/validateFields/getFieldsValue/getFieldValue all gone
   - form.handleSubmit gone, validation is one if (!name.trim()) check
   - The page never rendered <Form> anyway — form lib was a value holder

2. features/script/components/ScriptGenerator.tsx
   - 8 useState hooks (topic/keywords/style/tone/length/audience/language/requirements)
   - buildFormValues() helper for handleGenerate payload
   - <Form onFinish> → native <form onSubmit>
   - 8 <FormItem> → direct <label> + controlled <Input>/<Select>/<RadioGroup>
   - 2 form.handleSubmit callsites → void handleGenerate()
   - Submit button onClick → htmlType=submit

3. shared/components/business/CompositionStudio/GlobalSettingsForm.tsx
   - Was a dead shell (Slider/InputNumber/Select had no value/onChange)
   - Now functional: 4 useState (frameDuration/effect/duration/easing) +
     onSave wire-up

4. shared/components/business/CompositionStudio/FrameEditForm.tsx
   - Same dead-shell pattern, now 12 useState + save/reset buttons
   - CameraMotion null → undefined (Select doesn't accept null value)

5. shared/components/business/CompositionStudio/index.tsx
   - 3 <FormItem label=> → plain <label> (no form state needed for skeleton)

Verification: lint 0 / tsc 0 / 1596 tests pass / build OK
react-vendor bundle dropped 311 KB → 287 KB (react-hook-form no longer
pulled into vendor chunk).
@Agions Agions merged commit 50b185e into main Jun 4, 2026
6 checks passed
@Agions Agions deleted the fix/form-antd-api-compat branch June 4, 2026 08:37
Agions added a commit that referenced this pull request Jun 4, 2026
Rename src/shared/components/ui/ui-components.tsx →
src/shared/components/ui/antd-compat-deprecated.tsx to reflect that
this file is the AntD-compatibility shim layer (not a generic UI module)
and is deprecated for new code.

The AntD Form bridge was already removed in PR #7 (commit 50b185e),
leaving this file as a historical artifact. The new name makes the
deprecation status visible in import statements, file listings, and
editor pickers.

Added a prominent @deprecated JSDoc block at the top with a full
migration guide mapping every re-export to its canonical shadcn/ui path.

Updated the backward-compat shim at src/components/ui/ui-components.tsx
to follow the new file location. All 7 existing callers continue to
work through the shim (zero-risk rename, no caller touched).

Verification: tsc 0 / lint 0 / 1596 tests pass / build OK.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant