Skip to content
Permalink
Browse files

Implement optimistic deletion of courses

* Add relevant action and reducer
* Wire them up in CoursesPage and CourseList components

Optimistic updates is an interesting concept meant to make UX fast and
responsive. In a standard async interaction, we call an API, wait for
it to return successfully, and then update our UI accordingly. However,
in the optimistic approach we update UI as soon as our async call is
initiated. We DO NOT wait for a successful response in order to make
things faster and snappier for the user. If an error does occur, we
handle it in catch() and show a relevant toast.
  • Loading branch information...
anuragbhd committed Jul 28, 2019
1 parent 2c6e47a commit 4a122fb9e9920c08f964ece0270adcb066026d6d
@@ -2,14 +2,15 @@ import React from "react";
import PropTypes from "prop-types";
import { Link } from "react-router-dom";

const CourseList = ({ courses }) => (
const CourseList = ({ courses, onDeleteClick }) => (
<table className="table">
<thead>
<tr>
<th />
<th>Title</th>
<th>Author</th>
<th>Category</th>
<th />
</tr>
</thead>
<tbody>
@@ -29,6 +30,14 @@ const CourseList = ({ courses }) => (
</td>
<td>{course.authorName}</td>
<td>{course.category}</td>
<td>
<button
className="btn btn-outline-danger"
onClick={() => onDeleteClick(course)}
>
Delete
</button>
</td>
</tr>
);
})}
@@ -37,7 +46,8 @@ const CourseList = ({ courses }) => (
);

CourseList.propTypes = {
courses: PropTypes.array.isRequired
courses: PropTypes.array.isRequired,
onDeleteClick: PropTypes.func.isRequired
};

export default CourseList;
@@ -7,6 +7,7 @@ import * as authorActions from "../../redux/actions/authorActions";
import CourseList from "./CourseList";
import Spinner from "../common/Spinner";
import { Redirect } from "react-router-dom";
import { toast } from "react-toastify";

class CoursesPage extends React.Component {
state = {
@@ -29,6 +30,13 @@ class CoursesPage extends React.Component {
}
}

handleDeleteCourse = course => {
toast.success("Course deleted.");
this.props.actions.deleteCourse(course).catch(error => {
toast.error(`Delete failed. ${error.message}`, { autoClose: false });
});
};

render() {
return (
<>
@@ -47,7 +55,10 @@ class CoursesPage extends React.Component {
Add Course
</button>

<CourseList courses={this.props.courses} />
<CourseList
courses={this.props.courses}
onDeleteClick={this.handleDeleteCourse}
/>
</>
)}
</>
@@ -63,7 +74,6 @@ CoursesPage.propTypes = {
};

function mapStateToProps(state) {
debugger;
return {
courses:
state.authors.length === 0
@@ -85,7 +95,8 @@ function mapDispatchToProps(dispatch) {
return {
actions: {
loadCourses: bindActionCreators(courseActions.loadCourses, dispatch),
loadAuthors: bindActionCreators(authorActions.loadAuthors, dispatch)
loadAuthors: bindActionCreators(authorActions.loadAuthors, dispatch),
deleteCourse: bindActionCreators(courseActions.deleteCourse, dispatch)
}
};
}
@@ -4,3 +4,4 @@ export const CREATE_COURSE_SUCCESS = "CREATE_COURSE_SUCCESS";
export const UPDATE_COURSE_SUCCESS = "UPDATE_COURSE_SUCCESS";
export const BEGIN_API_CALL = "BEGIN_API_CALL";
export const API_CALL_ERROR = "API_CALL_ERROR";
export const DELETE_COURSE_OPTIMISTIC = "DELETE_COURSE_OPTIMISTIC";
@@ -36,3 +36,10 @@ export function saveCourse(course) {
});
};
}

export function deleteCourse(course) {
return function(dispatch) {
dispatch({ type: types.DELETE_COURSE_OPTIMISTIC, course });
return courseAPI.deleteCourse(course.id);
};
}
@@ -11,6 +11,8 @@ export default function courseReducer(state = initialState.courses, action) {
);
case types.LOAD_COURSES_SUCCESS:
return action.courses;
case types.DELETE_COURSE_OPTIMISTIC:
return state.filter(course => course.id !== action.course.id);
default:
return state;
}

0 comments on commit 4a122fb

Please sign in to comment.
You can’t perform that action at this time.