Skip to content

Edit a Contact

Pamela Argentino edited this page Feb 16, 2017 · 6 revisions

Up until now, we have most of the CRUD functionalities going on: we can create new contacts, delete existing contacts and list them to the screen. Now we need only one thing: to be able to update contact data.

For that, we will cover mostly what's remaining in our guide: dynamic routes with react-router, component lifecycle methods and more state manipulation with Redux.

Adding an unique identifier to the Contact object

In order to identify which record we're going to edit, we need a unique ID that sets the contact records apart, since none of the fields are truly unique.

For that, we can use the uuid library that can be installed via yarn.

yarn add uuid
nano src/actions/index.js
import uuid from 'uuid';
...
export const contactAdd = contact => ({
  type: 'CONTACT_ADD',
  contact: {
    id: uuid.v4(),
    ...contact
  }
});
...

Coming up with ContactEdit

To implement the new update functionality, we need to create a new action, CONTACT_EDIT with the updated contact as argument and wire it up on contactsReducer. Here's how we're going to make it.

nano src/actions/index.js
...
export const contactEdit = contact => ({
  type: 'CONTACT_EDIT',
  contact
});
nano src/reducers/contactsReducer.js
...
    case 'CONTACT_EDIT':
      const index = state.contactList.findIndex(c => c.id === action.contact.id);
      return {
        ...state,
        contactList: [
          ...state.contactList.slice(0, index),
          action.contact,
          ...state.contactList.slice(index + 1)
        ]
      };
...

In the example above, we're looking for the index of the object that matches the unique ID to the one that we provided through the action and sliding it into the new state.

Now let's come up with the ContactEdit stateful component. The route we will assign to ContactEdit has a dynamic parameter that we can access in the component via this.props.match.params. In this example, since we still don't have a backend server doing the database work for us, we will also include the contact list in the state so we can perform a search, but it will be removed later on.

We will also use a component lifecycle method called componentWillMount that's automatically called prior to the first time the component renders on the screen. Inside it, we will locate the right contact based on the contactId passed via route (that we will create later) and update the Redux state accordingly.

nano src/containers/ContactEdit.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link, withRouter } from 'react-router-dom';
import { contactFormUpdate, contactEdit } from '../actions';
import ContactForm from './ContactForm';

class ContactEdit extends Component {
  state = {
    contact: {}
  }

  componentWillMount() {
    const { contactId } = this.props.match.params;

    const contact = this.props.contactList.find(c => c.id === contactId);

    this.setState({ contact });
  }

  handleSubmit(values) {
    const { id, name, phone, email } = values;

    this.props.onSubmit({ id, name, phone, email });
    this.props.push("/");
  }

  render() {
    return (
      <ContactForm onSubmit={this.handleSubmit.bind(this)} initialValues={this.state.contact} />
    )
  }
}

const mapStateToProps = state => ({
  ...state.contacts
});

const mapDispatchToProps = dispatch => ({
  onChange: ({ prop, value }) => dispatch(contactFormUpdate({ prop, value })),
  onSubmit: contact => dispatch(contactEdit(contact))
});

export default connect(mapStateToProps, mapDispatchToProps)(withRouter(ContactEdit));
nano src/containers/ContactForm.js
...
<form ...>
  <Field type="hidden" component="input" name="id" />
...

The id field is needed to store the newly created ID that will be needed to edit a contact.

Now that the ContactEdit component is ready, we need to set up a route for it on App.

nano src/App.js
...
import ContactEdit from './containers/ContactEdit';
...
<Route path="/edit/:contactId" component={ContactEdit} />
...

/edit/:contactId means that anything can go after /edit/ and that value will be captured by the component through this.props.params.contactId, which we did in the ContactEdit component. So, everytime we need to edit a contact, we just need to pass the unique ID after /edit/ and ContactEdit will know it.

Now, let's include an edit link to the ContactList component.

nano src/containers/ContactList.js
...
    <th colSpan={2}>&nbsp;</th>
  </tr>
</thead>
...
{
  this.props.contactList.map(({ id, name, phone, email }, index) => (
    <tr key={index}>
      ...
      <td>
        <Link to={`/edit/${id}`} className="btn btn-primary">Edit</Link>
      </td>
      <td>
        <Button
          buttonType="btn-danger"
          onClick={() => this.props.onClickDelete(index)}
        >
          Delete
        </Button>
      </td>
    </tr>
  ))
}
...

Save, run yarn start and test.

Now we have a complete CRUD... except we still don't have a database. Commit the changes and let's do the next step.

git add .
git commit -m "Add an edit form and new routes to the app"

Next step: Create a back-end server and database

Clone this wiki locally