React + Redux Tutorial Part VIII: The Delete Feature

The Delete Cat Feature


This is Part VIII of a eight part series on building a CRUD application with React + Redux. You can view the code for this project here. You can view the table of contents here


Our user should be able to delete a cat by clicking a "delete" button displayed alongside the cat's details. This should remove the cat from the DOM and redirect the user back to the cats index view. Something like this:

We'll manage our delete feature from the CatPage component. We'll add a button to delete a cat when we display the cat's details, and define a function that gets fired by the click of that button.

That function will dispatch an action that we'll define to send a delete request to the API.

// src/components/cats/CatPage.js

...

class CatPage extends React.Component {  
  constructor(props, context) {
    super(props, context);
    this.state = {
      cat: this.props.cat, 
      catHobbies: this.props.catHobbies,
      checkBoxHobbies: props.checkBoxHobbies,
      saving: false,
      isEditing: false
    };
    this.saveCat = this.saveCat.bind(this);
    this.updateCatState = this.updateCatState.bind(this);
    this.updateCatHobbies = this.updateCatHobbies.bind(this);
    this.toggleEdit = this.toggleEdit.bind(this);
    this.deleteCat = this.deleteCat.bind(this);
  }
  ...

  deleteCat(event) {
    this.props.actions.deleteCat(this.state.cat)
  }

  render() {
    ...
    return (
      <div className="col-md-8 col-md-offset-2">
        <h1>{this.state.cat.name}</h1>
        <p>breed: {this.state.cat.breed}</p>
        <p>weight: {this.state.cat.weight}</p>
        <p>temperament: {this.state.cat.temperament}</p>
        <HobbyList hobbies={this.state.catHobbies} />
        <button 
           onClick={this.toggleEdit} 
           className="btn btn-default">
           edit
        </button>
        <button 
           onClick={this.deleteCat} 
           className="btn btn-default">
           delete
       </button>
      </div>
    )
  }
...

Let's define our deleteCat action and its corresponding CatApi function.

The deleteCat Action Creator

First, we'll define our CatApi function:

// src/api/catApi.js

class CatApi {  
  ...
   static deleteCat(cat) {
    const request = new Request(`http://localhost:5000/api/v1/cats/${cat.id}`, {
      method: 'DELETE'
    });

    return fetch(request).then(response => {
      return response.json();
    }).catch(error => {
      return error;
    });
  }
}

export default CatsApi;  

Now, our action creator:

...

export function deleteCatSuccess(cat) {  
  return {type: types.DELETE_CAT_SUCCESS, cat}
}

export function deleteCat(cat) {  
  return function(dispatch) {
    return catApi.deleteCat(cat).then(() => {
      console.log(`Deleted ${cat.id}`)
      dispatch(deleteCatSuccess(cat));
      return;
    }).catch(error => {
      throw(error);
    })
  }

We'll add the DELETE_CAT_SUCCESS constant to our list of action types constants:

// src/actions/actionTypes.js

export const LOAD_CATS_SUCCESS = 'LOAD_CATS_SUCCESS';  
export const LOAD_HOBBIES_SUCCESS = 'LOAD_HOBBIES_SUCCESS';  
export const UPDATE_CAT_SUCCESS = 'UPDATE_CAT_SUCCESS';  
export const CREATE_CAT_SUCCESS = 'CREATE_CAT_SUCCESS';  
export const DELETE_CAT_SUCCESS = 'DELETE_CAT_SUCCESS';  

Lastly, we'll tell our cat reducer what to do when it receives the DELETE_CAT_SUCCESS action type:

// src/reducers/catReducer.js

import * as types from '../actions/actionTypes';  
import initialState from './initialState';  
import {browserHistory} from 'react-router';


export default function catReducer(state = initialState.cats, action) {  
  switch(action.type) {
    case types.LOAD_CATS_SUCCESS:
     return action.cats;
    case types.CREATE_CAT_SUCCESS:
      browserHistory.push(`/cats/${action.cat.id}`)
      return [
        ...state.filter(cat => cat.id !== action.cat.id),
        Object.assign({}, action.cat)
      ]
    case types.UPDATE_CAT_SUCCESS:
      return [
        ...state.filter(cat => cat.id !== action.cat.id),
        Object.assign({}, action.cat)
      ]
    case types.DELETE_CAT_SUCCESS: {
      const newState = Object.assign([], state);
      const indexOfCatToDelete = state.findIndex(cat => {
        return cat.id == action.cat.id
      })
      newState.splice(indexOfCatToDelete, 1);
      browserHistory.push('/cats');
      return newState;
    }
    default: 
      return state;
  }
}

The cat reducer responds to this action by creating a new copy of application state, with the deleted cat removed. The cat reducer will also redirect us back to the cats index page, rendering our CatsPage component.

And that's it for our delete feature!

Conclusion

That concludes this eight part series on building CRUD in React + Redux. As I was moving through the construction of this project, I found myself wishing for a comprehensive resource that could take me through all the common CRUD features, linking up to an external API. This has been my own attempt to provide such a resource, offering my own solution to several common problems along the way.

I still have a lot to learn and I'm hoping to you, the good people of the internet, will graciously point out any (many) of the mistakes I may have made here. One area in particular that I'm still struggling with is the concept of state immutability. I suspect that I'm mutating state in some of my reducers, and I ended up electing not to use (Redux Immutable State Invariant middleware)[https://github.com/leoasis/redux-immutable-state-invariant] when I couldn't debug some of the errors it was throwing.

In any case, I hope you find this resource helpful, and don't hesitate to hit me up with questions/comments/corrections/cat gifs.

comments powered by Disqus
comments powered by Disqus