Repository navigation
Set up Contact Form
In the last step, we managed to create a component that lists all contacts in our state, but we didn't
have any way of creating such contacts. Let's create a form that inserts such contacts into our list
and have ContactList render them.
To create a Redux Form, instead of wrapping it with react-redux's connect function, we will wrap
it with a reduxForm function provided by redux-form. We will also use its components to create our
form fields.
nano src/containers/ContactForm.jsimport React, { Component } from 'react';
import { reduxForm, Field } from 'redux-form';
class ContactForm extends Component {
render() {
const { handleSubmit, pristine, submitting, reset } = this.props;
return (
<form onSubmit={handleSubmit}>
<fieldset className="form-group">
<label htmlFor="name" className="control-label">Name</label>
<Field name="name" component="input" type="text" placeholder="e.g. John Doe" props={{ className: 'form-control' }} />
</fieldset>
<fieldset className="form-group">
<label htmlFor="name" className="control-label">Phone</label>
<Field name="phone" component="input" type="text" placeholder="(111) 111-1111" props={{ className: 'form-control' }} />
</fieldset>
<fieldset className="form-group">
<label htmlFor="name" className="control-label">Email</label>
<Field name="email" component="input" type="email" placeholder="john.doe@example.com" props={{ className: 'form-control' }} />
</fieldset>
<fieldset>
<button type="submit" className="btn btn-primary" disabled={pristine || submitting}>Submit</button>
<button type="button" className="btn btn-default" onClick={reset} disabled={pristine || submitting}>Reset</button>
</fieldset>
</form>
)
}
}
export default reduxForm({
form: 'contact' // This is the form's name and must be unique across the app
})(ContactForm);The Field component will render the component we pass to it wrapped in a higher order component containing
multiple properties, such as the ones we pass to it (placeholder, type, custom props via props, etc.)
as well as additional metadata such as if the field has already been focused (touched) or if there are validation
errors, which we'll cover in another lesson.
To render ContactForm, we'll create a ContactCreate component that will handle the rendering of ContactForm
and implement a submit function for it.
nano src/containers/ContactCreate.jsimport React, { Component } from 'react';
import { connect } from 'react-redux';
import ContactForm from './ContactForm';
class ContactCreate extends Component {
handleSubmit(values) {
console.log(values);
}
render() {
return (
<ContactForm onSubmit={this.handleSubmit.bind(this)} />
);
}
}
const mapDispatchToProps = dispatch => ({});
export default connect(null, mapDispatchToProps)(ContactCreate);Note that we used a handleSubmit function from this.props in ContactForm, yet we're passing an
onSubmit prop to it on ContactCreate. That's because redux-form will wrap this.props.onSubmit into
this.props.handleSubmit, and the latter passes as argument a JSON object containing the values.
A good practice to follow when creating actions is to have a function receive as arguments whatever payload the action needs to pass to the reducer and return the action object with the appropriate type and payload (also known as Action Creator). This ensures that you're always using the right action type for what you want to do.
mkdir src/actions
nano src/actions/index.jsexport const contactAdd = contact => ({
type: 'CONTACT_ADD',
contact
});ES6 has a feature called enhanced object literals that allow you to declare an object passing only the
variable if its name is also the object key, meaning { contact } is the same as { contact: contact }.
This also applies if you're receiving an object as a parameter, in which case it's called object destructuring,
e.g. ({ val1, val2 }) => console.log(val1, val2) is the same as (object) => { const { val1, val2 } = object; console.log(val1, val2); } It's a way to shorten your code and make it look better and more readable.
Now that we have an action creator, we need to make our reducer understand the action being dispatched
to it. For that, we're going to include the action type in that switch statement that we have put
into the reducer.
nano src/reducers/contactsReducer.js...
case 'CONTACT_ADD':
return {
...state,
contactList: state.contactList.concat(action.contact)
};
...We're using Array.prototype.concat here instead of Array.prototype.push because concat returns a new
array containing all previous elements and the new element appended to it, whereas push modifies the array
in memory and doesn't return anything, which goes against the rule of state immutability.
nano src/containers/ContactCreate.js...
import { contactAdd } from '../actions';
...
handleSubmit({ name, phone, email }) {
this.props.onSubmit({ name, phone, email });
}
...
const mapDispatchToProps = dispatch => ({
onSubmit: contact => dispatch(contactAdd(contact))
});
...We're testing this component in the next step.
Next step: Set up Routes