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 template
-
templates/validation-converter.ts
-
Validation logic converter utility
Reference Files
-
references/migration-comparison.md
-
Side-by-side comparison table
-
references/common-pitfalls.md
-
Migration pitfalls and solutions
-
references/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