Migration from Redux Form
Good news! React Final Form was written by the same guy (@erikras) that wrote Redux Form, so much of the API is exactly the same. The primary difference is that, rather than "decorate" your form component with a Higher Order Component, you use React Final Form's <Form/>
component to give you all your form state via a render prop. Most of the config
properties from Redux Form maps directly onto the props to <Form/>
, e.g. initialValues
, onSubmit
, validate
, etc.
Step 1: Change your imports
import React from 'react'
-import { reduxForm, Field } from 'redux-form'
+import { Form, Field } from 'react-final-form'
Step 2: Surround your <form/>
with a <Form/>
import React from 'react'
import { Form, Field } from 'react-final-form'
const MyForm = props => {
const { handleSubmit, pristine, reset, submitting } = props
return (
+ <Form>
+ {() => (
<form onSubmit={handleSubmit}>
...fields here...
</form>
+ )}
+ </Form>
)
}
Step 3: Move all your config
values from reduxForm()
to be props of <Form/>
import React from 'react'
import { Form, Field } from 'react-final-form'
const MyForm = props => {
const { handleSubmit, pristine, reset, submitting } = props
return (
<Form
+ initialValues={{
+ firstName: 'Dan'
+ }}
+ onSubmit={values => {
+ // send values to the cloud
+ }}
+ validate={values => {
+ // do validation here, and return errors object
+ }}
>
{() => (
<form onSubmit={handleSubmit}>
...fields here...
</form>
)}
</Form>
)
}
export default reduxForm({
form: 'myForm',
- initialValues: {
- firstName: 'Dan'
- },
- onSubmit: values => {
- // send values to the cloud
- },
- validate: values => {
- // do validation here, and return errors object
- }
})(MyForm)
Step 4: Get form state from <Form/>
, not as props
import React from 'react'
import { Form, Field } from 'react-final-form'
const MyForm = props => {
- const { handleSubmit, pristine, reset, submitting } = props
return (
<Form
initialValues={{
firstName: 'Dan'
}}
onSubmit={values => {
// send values to the cloud
}}
validate={values => {
// do validation here, and return errors object
}}
>
+ {({ handleSubmit, pristine, reset, submitting }) => (
<form onSubmit={handleSubmit}>
...fields here...
</form>
)}
</Form>
)
}
export default reduxForm({
form: 'myForm'
})(MyForm)
Step 5: Observe API changes
Some of the API is slightly different. For example, rather than providing a reset
function, the entire FormApi
object is provided, on which reset()
is a function.
import React from 'react'
import { Form, Field } from 'react-final-form'
const MyForm = props => {
return (
<Form
initialValues={{
firstName: 'Dan'
}}
onSubmit={values => {
// send values to the cloud
}}
validate={values => {
// do validation here, and return errors object
}}
>
- {({ handleSubmit, pristine, reset, submitting }) => (
+ {({ handleSubmit, pristine, form, submitting }) => (
<form onSubmit={handleSubmit}>
...fields here...
<div>
<button type="submit" disabled={submitting}>
Submit
</button>
<button
type="button"
disabled={pristine || submitting}
- onClick={reset}
+ onClick={form.reset}
>
Clear Values
</button>
</div>
</form>
)}
</Form>
)
}
export default reduxForm({
form: 'myForm'
})(MyForm)
Step 6: Remove the HOC
Now you can just export your component. No HOC decorator needed! Why?
-export default reduxForm({
- form: 'myForm'
-})(MyForm)
+export default MyForm
Step 7: Error management
No more SubmissionError
to throw, error management is made with the return value.
Have a look here to know more: https://final-form.org/docs/react-final-form/types/FormProps#onsubmit
Step 8: Define your renderField
functions inline (if you want)
With Redux Form, it was common to have to do something like this:
// outside your render() method
const renderField = (field) => (
<div className="input-row">
<input {...field.input} type="text"/>
{field.meta.touched && field.meta.error &&
<span className="error">{field.meta.error}</span>}
</div>
)
// inside your render() method
<Field name="myField" component={renderField}/>
With React Final Form, you can define your render logic directly your JSX.
// inside your render() method
<Field name="myField">
{field => (
<div className="input-row">
<input {...field.input} type="text" />
{field.meta.touched && field.meta.error && (
<span className="error">{field.meta.error}</span>
)}
</div>
)}
</Field>
It's a little easier to read, but it's less reusable. Large projects are going to create reusable field render functions. If you're migrating a mature project, you can probably continue to use your existing functions/components. <Field/>
provides the same { input, meta }
shape that Redux Form does.
The Full Diff
import React from 'react'
-import { reduxForm, Field } from 'redux-form',
+import { Form, Field } from 'react-final-form'
const MyForm = props => {
- const { handleSubmit, pristine, reset, submitting } = props
return (
+ <Form
+ initialValues={{
+ firstName: 'Dan'
+ }}
+ onSubmit={values => {
+ // send values to the cloud
+ }}
+ validate={values => {
+ // do validation here, and return errors object
+ }}
+ >
+ {({ handleSubmit, pristine, form, submitting }) => (
<form onSubmit={handleSubmit}>
<div>
<Field
name="firstName"
component="input"
type="text"
placeholder="First Name"
/>
</div>
<div>
<label>Last Name</label>
<Field
name="lastName"
component="input"
type="text"
placeholder="Last Name"
/>
</div>
<div>
<button type="submit" disabled={submitting}>
Submit
</button>
<button
type="button"
disabled={pristine || submitting}
- onClick={reset}
+ onClick={form.reset}
>
Clear Values
</button>
</div>
</form>
+ )}
+ </Form>
)
}
-export default reduxForm({
- form: 'myForm',
- initialValues: {
- firstName: 'Dan'
- },
- onSubmit: values => {
- // send values to the cloud
- },
- validate: values => {
- // do validation here, and return errors object
- }
-})(MyForm)
+export default MyForm
<Fields/>
If you're accustomed to being able to get the state of many fields at once using Redux Form's <Fields/>
, you may be wondering why this library does not include it. The answer is that most people don't need it, but if you do, you can write it yourself recursively, like this:
const Fields = ({
names,
subscription,
fieldsState = {},
children,
originalRender
}) => {
if (!names.length) {
return (originalRender || children)(fieldsState)
}
const [name, ...rest] = names
return (
<Field name={name} subscription={subscription}>
{fieldState => (
<Fields
names={rest}
subscription={subscription}
originalRender={originalRender || children}
fieldsState={{ ...fieldsState, [name]: fieldState }}
/>
)}
</Field>
)
}
Here's a sandbox demonstrating its usage:
<FieldArray/>
React Final Form does not come with field arrays right out of the box. This is because many projects do not need them, and the philosophy of React Final Form is to keep bundle size small, but provide ways to add additional functionality when you need it.
You will need to install two additional packages, final-form-arrays
, which provides array functionality to the core Final Form instance, and react-final-form-arrays
, which contains the <FieldArray/>
component. If you are accustomed to using Redux Form's <FieldArray/>
component, you already know the API for React Final Form's <FieldArray/>
component. It injects an "array-like" object called fields
, which you can map()
over to get the string
names for each of the fields in the array. You pass these field names to the <Field/>
component to render one of the fields in your array. It works just like in Redux Form.
import arrayMutators from 'final-form-arrays'
import { FieldArray } from 'react-final-form-arrays'
const MyForm = () => (
<Form
onSubmit={onSubmit}
mutators={{ ...arrayMutators }}
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ IMPORTANT!
>
{({
handleSubmit,
form: {
mutators: { push, pop } // injected from final-form-arrays above
}
}) => (
<form onSubmit={handleSubmit}>
... other fields here maybe ...
<button type="button" onClick={() => push('customers', undefined)}>
Add Customer
</button>
<button type="button" onClick={() => pop('customers')}>
Remove Customer
</button>
<FieldArray name="customers">
{({ fields }) =>
fields.map((name, index) => (
<div key={name}>
<label>Cust. #{index + 1}</label>
<Field
name={`${name}.firstName`}
component="input"
placeholder="First Name"
/>
</div>
))
}
</FieldArray>
</form>
)}
</Form>
)
Here's a sandbox to demonstrate:
Cheat Code: The Lazy Way
⚠️ NOT RECOMMENDED ⚠️
There's some chance that you could get away with implementing this function that replicates what the reduxForm()
decorator does:
import { Form } from 'react-final-form'
const reactFinalForm = ({ form, ...config }) => component => props => (
<Form {...config} {...props} component={component} />
)
Then you'd only have to change that one thing.
-export default reduxForm({
+export default reactFinalForm({
form: 'myForm',
initialValues: {
firstName: 'Dan'
},
onSubmit: values => {
// send values to the cloud
},
validate: values => {
// do validation here, and return errors object
}
})(MyForm)
But cheats like this create technical debt, so do the right thing and refactor your forms to use render props. 😄