Skip to content

Set up Contact Form

Pamela Argentino edited this page Feb 16, 2017 · 1 revision

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.

Creating a Redux Form

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.

Creating the ContactForm component

nano src/containers/ContactForm.js
import 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.js
import 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.

Creating Actions

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.js
export const contactAdd = contact => ({
  type: 'CONTACT_ADD',
  contact
});
Tidbit

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.

Implementing actions to reducer

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.

Dispatching the action

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

Clone this wiki locally