- Why not Redux-Form or Formik?
- Why no HOC?
- How can I trigger a submit from outside my form?
- Why can't I have numeric keys in an object?
- I'm changing form state on first render, e.g. with FormSpy, why are my changes not reflected?
Those are both excellent form libraries. Like all engineering decisions, it depends on your requirements and what trade-offs you wish to make.
The only benefit that higher order components provide over render props is
access to the injected props from within component lifecycle methods. Plus, it
only takes a single line of code to transform a component with a render
(or
component
) prop into a HOC. If you really want a HOC, you can write your own:
import { Form, Field } from 'react-final-form'
class MyForm extends React.Component {
componentDidMount() {
const { initialize } = this.props // access to injected props
ajax.fetch('/myData').then(data => initialize(data))
}
render() {
return <form onSubmit={this.props.handleSubmit}>...some fields...</form>
}
}
// 👇 THIS LINE IS THE HOC 👇
export default props => <Form {...props} component={MyForm} />
Doing a HOC
properly,
as a library should, with hoisted statics and displayName
and ref
, etc., is
a hassle and would add unnecessary bulk.
This is a common question I see from people migrating from redux-form
. There
are three possible solutions:
You can use the DOM to get a reference to your <form>
element and dispatch a
submit event on it. Note that you cannot just call submit()
, as this will not
trigger React's event handlers.
<button onClick={() => {
document.getElementById('myForm').submit() // ❌
}}>Submit</button>
<button onClick={() => {
document.getElementById('myForm').dispatchEvent(new Event('submit', { cancelable: true })) // ✅
}}>Submit</button>
<form id="myForm" onSubmit={handleSubmit}>
...fields go here...
</form>
See Sandbox Example.
If you define a variable outside of your form, you can then set the value of
that variable to the handleSubmit
function that 🏁 React Final Form gives you,
and then you can call that function from outside of the form.
let submit
return (
<div>
<button onClick={submit}>Submit</button> // ❌ Not overwritten closure value
<button onClick={event => submit(event)}>Submit</button> // ✅
<Form
onSubmit={onSubmit}
render={({ handleSubmit }) => {
submit = handleSubmit
return <form>...fields go here...</form>
}}
/>
</div>
)
See Sandbox Example.
If you're already using Redux, you could potentially use the same mechanism that
redux-form
uses:
Redux Dead Drop.
So you want to have value structured like { 13: 'Bad Luck', 42: 'Meaning of Everything' }
, but you're getting an error. This is because the setIn
engine in 🏁 Final Form uses the isNaN
-ness of keys to determine whether or not it should create an object or an array when constructing deep data structures. Adding checks for bracket[3]
syntax as opposed to dot.3
syntax adds a lot of complexity, and has consciously been avoided.
You will need to convert all your keys to NaN
strings before initializing your form values, and then convert them back to numbers on submit. It's not that hard.
const stringifyKeys = values =>
Object.keys(values).reduce((result, key) => {
result[`key${key}`] = values[key]
return result
}, {})
const destringifyKeys = values =>
Object.keys(values).reduce((result, key) => {
result[Number(key.substring(3))] = values[key]
return result
}, {})
<Form
onSubmit={values => onSubmit(destringifyKeys(values))}
initialValues={stringifyKeys(initialValues)}>
...
</Form>
As of v5
, the only changes that occur during the first render that will cause the form to rerender are if adding field-level validation functions changes the validity of the form. You will need to put your side effect into either a React useEffect
hook, or just call it in a setTimeout
to allow the form to finish rendering and setting up its side effects before you go altering the state.