Formily Migration Guide
Formily Migration Guide
This skill provides comprehensive guidance for converting existing React forms to Formily. Focus on migration strategies, common patterns, and step-by-step conversion processes for different form libraries and vanilla React forms with TypeScript.
Migration Overview
Why Migrate to Formily?
- Better TypeScript support - Type-safe form management
- Schema-driven approach - Declarative form definitions
- Improved performance - Efficient reactivity and minimal re-renders
- Powerful validation - Built-in validation with custom rules
- Better developer experience - Less boilerplate, more maintainability
Migration Strategies
- Incremental migration - Convert forms one at a time
- Parallel development - Build new forms with Formily while maintaining existing ones
- Complete rewrite - Replace entire form system at once
From Vanilla React Forms
Before: Vanilla React Form
import React, { useState, FormEvent } from 'react'
interface ContactForm {
name: string
email: string
message: string
}
const VanillaContactForm: React.FC = () => {
const [formData, setFormData] = useState<ContactForm>({
name: '',
email: '',
message: ''
})
const [errors, setErrors] = useState<Partial<ContactForm>>({})
const [isSubmitting, setIsSubmitting] = useState(false)
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const { name, value } = e.target
setFormData(prev => ({ ...prev, [name]: value }))
// Clear error when user starts typing
if (errors[name as keyof ContactForm]) {
setErrors(prev => ({ ...prev, [name]: '' }))
}
}
const validateForm = (): boolean => {
const newErrors: Partial<ContactForm> = {}
if (!formData.name.trim()) {
newErrors.name = 'Name is required'
}
if (!formData.email.trim()) {
newErrors.email = 'Email is required'
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
newErrors.email = 'Invalid email format'
}
if (!formData.message.trim()) {
newErrors.message = 'Message is required'
}
setErrors(newErrors)
return Object.keys(newErrors).length === 0
}
const handleSubmit = async (e: FormEvent) => {
e.preventDefault()
if (!validateForm()) {
return
}
setIsSubmitting(true)
try {
// Submit form data
await submitForm(formData)
alert('Form submitted successfully!')
setFormData({ name: '', email: '', message: '' })
} catch (error) {
console.error('Submission error:', error)
alert('Submission failed. Please try again.')
} finally {
setIsSubmitting(false)
}
}
return (
<form onSubmit={handleSubmit}>
<div>
<label>Name:</label>
<input
type="text"
name="name"
value={formData.name}
onChange={handleChange}
/>
{errors.name && <span className="error">{errors.name}</span>}
</div>
<div>
<label>Email:</label>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
/>
{errors.email && <span className="error">{errors.email}</span>}
</div>
<div>
<label>Message:</label>
<textarea
name="message"
value={formData.message}
onChange={handleChange}
/>
{errors.message && <span className="error">{errors.message}</span>}
</div>
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Submitting...' : 'Submit'}
</button>
</form>
)
}
After: Formily Version
import React from 'react'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
import { Form, FormItem, Input, Button } from '@formily/antd'
interface ContactForm {
name: string
email: string
message: string
}
const FormilyContactForm: React.FC = () => {
const form = createForm<ContactForm>({
initialValues: {
name: '',
email: '',
message: ''
},
validateFirst: true
})
const schema = {
type: 'object',
properties: {
name: {
type: 'string',
title: 'Name',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-validator': [
{ required: true, message: 'Name is required' }
]
},
email: {
type: 'string',
title: 'Email',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-validator': [
{ required: true, message: 'Email is required' },
{ format: 'email', message: 'Invalid email format' }
]
},
message: {
type: 'string',
title: 'Message',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input.TextArea',
'x-validator': [
{ required: true, message: 'Message is required' }
]
}
}
}
const SchemaField = createSchemaField({
components: {
Form,
FormItem,
Input,
Button
}
})
const handleSubmit = async () => {
try {
await form.validate()
const values = form.values
await submitForm(values)
alert('Form submitted successfully!')
form.reset()
} catch (error) {
console.error('Validation errors:', error)
}
}
return (
<FormProvider form={form}>
<Form labelCol={6} wrapperCol={16}>
<SchemaField schema={schema} />
<FormItem wrapperCol={{ offset: 6, span: 16 }}>
<Button type="primary" onClick={handleSubmit}>
Submit
</Button>
</FormItem>
</Form>
</FormProvider>
)
}
From Formik
Before: Formik Form
import React from 'react'
import { Formik, Form, Field, ErrorMessage } from 'formik'
import * as Yup from 'yup'
const FormikExample: React.FC = () => {
const initialValues = {
firstName: '',
lastName: '',
email: '',
age: ''
}
const validationSchema = Yup.object({
firstName: Yup.string().required('First name is required'),
lastName: Yup.string().required('Last name is required'),
email: Yup.string().email('Invalid email').required('Email is required'),
age: Yup.number().min(18, 'Must be at least 18').required('Age is required')
})
const handleSubmit = (values: any, { setSubmitting }: any) => {
submitForm(values)
setSubmitting(false)
}
return (
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={handleSubmit}
>
{({ isSubmitting }) => (
<Form>
<div>
<label>First Name</label>
<Field name="firstName" type="text" />
<ErrorMessage name="firstName" component="div" />
</div>
<div>
<label>Last Name</label>
<Field name="lastName" type="text" />
<ErrorMessage name="lastName" component="div" />
</div>
<div>
<label>Email</label>
<Field name="email" type="email" />
<ErrorMessage name="email" component="div" />
</div>
<div>
<label>Age</label>
<Field name="age" type="number" />
<ErrorMessage name="age" component="div" />
</div>
<button type="submit" disabled={isSubmitting}>
Submit
</button>
</Form>
)}
</Formik>
)
}
After: Formily Version
import React from 'react'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
import { Form, FormItem, Input, InputNumber, Button } from '@formily/antd'
const FormilyFormikExample: React.FC = () => {
const form = createForm({
initialValues: {
firstName: '',
lastName: '',
email: '',
age: undefined
}
})
const schema = {
type: 'object',
properties: {
firstName: {
type: 'string',
title: 'First Name',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-validator': [{ required: true, message: 'First name is required' }]
},
lastName: {
type: 'string',
title: 'Last Name',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-validator': [{ required: true, message: 'Last name is required' }]
},
email: {
type: 'string',
title: 'Email',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-validator': [
{ required: true, message: 'Email is required' },
{ format: 'email', message: 'Invalid email format' }
]
},
age: {
type: 'number',
title: 'Age',
required: true,
'x-decorator': 'FormItem',
'x-component': 'InputNumber',
'x-validator': [
{ required: true, message: 'Age is required' },
{ minimum: 18, message: 'Must be at least 18' }
]
}
}
}
const SchemaField = createSchemaField({
components: {
Form,
FormItem,
Input,
InputNumber,
Button
}
})
const handleSubmit = async () => {
try {
await form.validate()
const values = form.values
await submitForm(values)
form.reset()
} catch (error) {
console.error('Validation errors:', error)
}
}
return (
<FormProvider form={form}>
<Form labelCol={6} wrapperCol={16}>
<SchemaField schema={schema} />
<FormItem wrapperCol={{ offset: 6, span: 16 }}>
<Button type="primary" onClick={handleSubmit}>
Submit
</Button>
</FormItem>
</Form>
</FormProvider>
)
}
From React Hook Form
Before: React Hook Form
import React from 'react'
import { useForm, SubmitHandler } from 'react-hook-form'
import { yupResolver } from '@hookform/resolvers/yup'
import * as Yup from 'yup'
interface FormData {
username: string
email: string
password: string
confirmPassword: string
}
const schema = Yup.object({
username: Yup.string().required('Username is required'),
email: Yup.string().email('Invalid email').required('Email is required'),
password: Yup.string().min(8, 'Password must be at least 8 characters').required('Password is required'),
confirmPassword: Yup.string().oneOf([Yup.ref('password')], 'Passwords must match').required('Confirm password is required')
})
const ReactHookFormExample: React.FC = () => {
const { register, handleSubmit, formState: { errors }, watch } = useForm<FormData>({
resolver: yupResolver(schema)
})
const watchedPassword = watch('password')
const onSubmit: SubmitHandler<FormData> = (data) => {
console.log(data)
// Submit logic
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label>Username</label>
<input {...register('username')} />
{errors.username && <span>{errors.username.message}</span>}
</div>
<div>
<label>Email</label>
<input type="email" {...register('email')} />
{errors.email && <span>{errors.email.message}</span>}
</div>
<div>
<label>Password</label>
<input type="password" {...register('password')} />
{errors.password && <span>{errors.password.message}</span>}
</div>
<div>
<label>Confirm Password</label>
<input type="password" {...register('confirmPassword')} />
{errors.confirmPassword && <span>{errors.confirmPassword.message}</span>}
</div>
<button type="submit">Submit</button>
</form>
)
}
After: Formily Version
import React from 'react'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
import { Form, FormItem, Input, Button } from '@formily/antd'
interface FormData {
username: string
email: string
password: string
confirmPassword: string
}
const FormilyReactHookFormExample: React.FC = () => {
const form = createForm<FormData>({
initialValues: {
username: '',
email: '',
password: '',
confirmPassword: ''
},
effects() {
// Custom validation for password match
onFieldChange('confirmPassword', ['value'], (field) => {
const password = form.values.password
const confirmPassword = field.value
if (confirmPassword && password !== confirmPassword) {
field.errors = ['Passwords must match']
} else {
field.errors = []
}
})
}
})
const schema = {
type: 'object',
properties: {
username: {
type: 'string',
title: 'Username',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-validator': [{ required: true, message: 'Username is required' }]
},
email: {
type: 'string',
title: 'Email',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-validator': [
{ required: true, message: 'Email is required' },
{ format: 'email', message: 'Invalid email format' }
]
},
password: {
type: 'string',
title: 'Password',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input.Password',
'x-validator': [
{ required: true, message: 'Password is required' },
{ min: 8, message: 'Password must be at least 8 characters' }
]
},
confirmPassword: {
type: 'string',
title: 'Confirm Password',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input.Password',
'x-validator': [{ required: true, message: 'Please confirm your password' }]
}
}
}
const SchemaField = createSchemaField({
components: {
Form,
FormItem,
Input,
Button
}
})
const handleSubmit = async () => {
try {
await form.validate()
const values = form.values
console.log(values)
// Submit logic
form.reset()
} catch (error) {
console.error('Validation errors:', error)
}
}
return (
<FormProvider form={form}>
<Form labelCol={6} wrapperCol={16}>
<SchemaField schema={schema} />
<FormItem wrapperCol={{ offset: 6, span: 16 }}>
<Button type="primary" onClick={handleSubmit}>
Submit
</Button>
</FormItem>
</Form>
</FormProvider>
)
}
Migration Checklist
Pre-Migration Preparation
-
Analyze existing forms
- Count total forms to migrate
- Identify form complexity levels
- Document validation logic
- Note custom components used
-
Set up Formily
- Install Formily packages
- Configure TypeScript types
- Set up Ant Design integration
- Create basic form components
-
Plan migration strategy
- Choose incremental vs complete rewrite
- Define migration timeline
- Assign team responsibilities
- Set up testing strategy
Migration Steps
-
Convert form structure
- Replace useState with createForm
- Convert field definitions to schema
- Update validation logic
- Replace onChange handlers
-
Update validation
- Convert Yup schemas to Formily validators
- Implement custom validation rules
- Set up async validation
- Configure error messages
-
Migrate components
- Replace Formik/React Hook Form components
- Update Ant Design integration
- Convert custom field components
- Update form submission logic
-
Test migration
- Unit test individual forms
- Integration test form workflows
- Test validation scenarios
- Performance test large forms
Post-Migration
-
Validation
- All forms render correctly
- Validation works as expected
- Form submission functions properly
- TypeScript types are correct
-
Optimization
- Remove unused dependencies
- Optimize form performance
- Update documentation
- Train team on Formily
Common Migration Patterns
State Management Migration
// Before: Multiple useState hooks
const [name, setName] = useState('')
const [email, setEmail] = useState('')
const [errors, setErrors] = useState({})
// After: Single form instance
const form = createForm({
initialValues: { name: '', email: '' }
})
Validation Migration
// Before: Manual validation
const validateForm = () => {
const errors = {}
if (!name) errors.name = 'Name is required'
if (!email) errors.email = 'Email is required'
setErrors(errors)
}
// After: Schema-based validation
const schema = {
type: 'object',
properties: {
name: { type: 'string', required: true },
email: { type: 'string', required: true, format: 'email' }
}
}
Form Submission Migration
// Before: Manual submission handling
const handleSubmit = (e) => {
e.preventDefault()
if (validateForm()) {
submitForm({ name, email })
}
}
// After: Formily submission
const handleSubmit = async () => {
await form.validate()
submitForm(form.values)
}
Additional Resources
Templates
templates/migration-template.tsx- Reusable migration templatetemplates/validation-converter.ts- Validation logic converter utility
Reference Files
references/migration-comparison.md- Side-by-side comparison tablereferences/common-pitfalls.md- Migration pitfalls and solutionsreferences/validation-mapping.md- Validation rule mapping guide
Migration Tools
- Use automated code migration scripts for simple forms
- Create custom transformation utilities for complex forms
- Implement gradual migration wrappers for mixed environments
Best Practices
- Start simple - Migrate basic forms first to learn the pattern
- Maintain type safety - Convert all forms to TypeScript
- Test thoroughly - Validate each migrated form matches original behavior
- Document changes - Keep track of migration decisions and patterns
- Iterate gradually - Improve forms after initial migration
- Monitor performance - Watch for performance issues in complex forms
- Train the team - Ensure all developers understand Formily patterns
- Plan for rollbacks - Keep original forms as backup during migration
More from whinc/my-claude-plugins
ahooks
Comprehensive ahooks React hooks library specialist. Expert in all 76+ ahooks hooks including state management, effects, data fetching, performance optimization, DOM utilities, and advanced patterns. Use when working with ahooks library, need React hooks utilities or want to learn best practices.
73formily ant design components
This skill should be used when the user asks to "use formily with ant design", "formily antd components", "formily ant design integration", "formily form layout", "formily form.item", "formily antd forms", or "integrate formily with ant design".
3