redux-form is a very compelling library for providing redux bindings for forms in a react application, which should be super-convenient. Unfortunately, using the library's own examples, I'm failing to actually bind anything, which is super in-convenient.
I'm attempting to make use of the sample code on the project site, and finding multiple obstacles, despite attempting to reproduce it faithfully. Where am I misinterpreting this API? Has the API shifted since the demo code was written? Am I missing some critical and obvious piece of redux knowledge?
Problem 1: the signature for the handleSubmit method should be handleSubmit(data)
. But handleSubmit is currently receiving only the React syntheticEvent from the submit action, and no data. (In fact, using the example as-written was sending two separate events, seemingly because of the stacked onSubmit
action on the form and the onClick
on the button.) Where is that data supposed to be coming from, and why am I failing to pass it to the handler?
Problem 2: there's a critical fields
object that must be defined on the form parent and supplied as prop to your form. Unfortunately, the shape of that fields
object is not explained in the docs, nor its purpose, really. Is it essentially the initial 'state' object? A simple object container for redux-form to use at runtime for errors, etc? I've gotten it to stop erroring by matching the props on fields
to the field names in connectReduxForm
, but because the data isn't binding, I'm assuming it's not the right shape.
Problem 3: The fields are supposed to be auto-bound to handlers for onBlur
and onChange
, so that they update the store appropriately. That's never happening. (Which we can see thanks to the Redux dev-tools. However, handleSubmit
is successfully dispatching the initialize
action, which suggests the store, reducer, and other basic plumbing are all working.)
Problem 4: validateContact
is firing once on init, but never again.
This is unfortunately too complex for a simple Fiddle, but the entire repo (it's just the basic ReduxStarterApp, plus this form POC) is available here.
And, here is the outer component:
import React from 'react';
import { connect } from 'react-redux';
import {initialize} from 'redux-form';
import ContactForm from '../components/simple-form/SimpleForm.js';
const mapStateToProps = (state) => ({
counter : state.counter
});
export class HomeView extends React.Component {
static propTypes = {
dispatch : React.PropTypes.func.isRequired,
counter : React.PropTypes.number
}
constructor () {
super();
}
handleSubmit(event, data) {
event.preventDefault();
console.log(event); // this should be the data, but is an event
console.log(data); // no data here, either...
console.log('Submission received!', data);
this.props.dispatch(initialize('contact', {})); // clear form: THIS works
return false;
}
_increment () {
this.props.dispatch({ type : 'COUNTER_INCREMENT' });
}
render () {
const fields = {
name: '',
address: '',
phone: ''
};
return (
<div className='container text-center'>
<h1>Welcome to the React Redux Starter Kit</h1>
<h2>Sample Counter: {this.props.counter}</h2>
<button className='btn btn-default'
onClick={::this._increment}>
Increment
</button>
<ContactForm handleSubmit={this.handleSubmit.bind(this)} fields={fields} />
</div>
);
}
}
export default connect(mapStateToProps)(HomeView);
And the inner form component:
import React, {Component, PropTypes} from 'react';
import {connectReduxForm} from 'redux-form';
function validateContact(data) {
console.log("validating");
console.log(data);
const errors = {};
if (!data.name) {
errors.name = 'Required';
}
if (data.address && data.address.length > 50) {
errors.address = 'Must be fewer than 50 characters';
}
if (!data.phone) {
errors.phone = 'Required';
} else if (!/\d{3}-\d{3}-\d{4}/.test(data.phone)) {
errors.phone = 'Phone must match the form "999-999-9999"';
}
return errors;
}
class ContactForm extends Component {
static propTypes = {
fields: PropTypes.object.isRequired,
handleSubmit: PropTypes.func.isRequired
}
render() {
const { fields: {name, address, phone}, handleSubmit } = this.props;
return (
<form onSubmit={handleSubmit}>
<label>Name</label>
<input type="text" {...name}/> {/* will pass value, onBlur and onChange */}
{name.error && name.touched && <div>{name.error}</div>}
<label>Address</label>
<input type="text" {...address}/> {/* will pass value, onBlur and onChange*/}
{address.error && address.touched && <div>{address.error}</div>}
<label>Phone</label>
<input type="text" {...phone}/> {/* will pass value, onBlur and onChange */}
{phone.error && phone.touched && <div>{phone.error}</div>}
<button type='submit'>Submit</button>
</form>
);
}
}
// apply connectReduxForm() and include synchronous validation
ContactForm = connectReduxForm({
form: 'contact', // the name of your form and the key to
// where your form's state will be mounted
fields: ['name', 'address', 'phone'], // a list of all your fields in your form
validate: validateContact // a synchronous validation function
})(ContactForm);
// export the wrapped component
export default ContactForm;
Thanks to Jonny Buchanan, who covered the most important point: don't do as I did and automatically assume that if props are required in your component, you must need to provide them yourself. The whole point of the higher-order function that is
connectReduxForm
is to provide them in the wrapper component. Fixing that immediately gave me event-handlers, for everything except Submit.The other critical oversight was here:
I didn't catch the point of that. But, the implementation is here:
The formReducer can't be referenced at
formReducer
, but requires the syntaxform: formReducer
. This was the correction that properly enabledhandleSubmit
.connectReduxForm
wraps your component with another component which handles passing in thefields
andhandleSubmit
props, but you're blowing those away by passing them in yourself.Try this instead (renamed the prop to
onSubmit
):And in
ContactForm
, pass your own submit handler to thehandleSubmit
function provided by redux-form:I recommend using the React developer tools to get a better picture of what's going on - you'll see how redux-form wraps your component and passes it a whole bunch of props, as documented in its README.