Optimistic delete

master
Tudor Stanciu 2020-04-12 02:10:57 +03:00
parent 5a85950b85
commit 82815b6d9a
5 changed files with 51 additions and 4 deletions

View File

@ -2,7 +2,7 @@ import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
const CourseList = ({ courses }) => ( const CourseList = ({ courses, onDeleteClick }) => (
<table className="table"> <table className="table">
<thead> <thead>
<tr> <tr>
@ -10,6 +10,7 @@ const CourseList = ({ courses }) => (
<th>Title</th> <th>Title</th>
<th>Author</th> <th>Author</th>
<th>Category</th> <th>Category</th>
<th />
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -29,6 +30,14 @@ const CourseList = ({ courses }) => (
</td> </td>
<td>{course.authorName}</td> <td>{course.authorName}</td>
<td>{course.category}</td> <td>{course.category}</td>
<td>
<button
className="btn btn-outline-danger"
onClick={() => onDeleteClick(course)}
>
Delete
</button>
</td>
</tr> </tr>
); );
})} })}
@ -37,7 +46,8 @@ const CourseList = ({ courses }) => (
); );
CourseList.propTypes = { CourseList.propTypes = {
courses: PropTypes.array.isRequired courses: PropTypes.array.isRequired,
onDeleteClick: PropTypes.func.isRequired
}; };
export default CourseList; export default CourseList;

View File

@ -7,6 +7,7 @@ import { bindActionCreators } from "redux";
import CourseList from "./CourseList"; import CourseList from "./CourseList";
import { Redirect } from "react-router-dom"; import { Redirect } from "react-router-dom";
import Spinner from "../common/Spinner"; import Spinner from "../common/Spinner";
import { toast } from "react-toastify";
class CoursesPage extends React.Component { class CoursesPage extends React.Component {
state = { state = {
@ -29,6 +30,15 @@ class CoursesPage extends React.Component {
} }
} }
handleDeleteCourse = async (course) => {
toast.success("Course deleted");
try {
await this.props.actions.deleteCourse(course);
} catch (error) {
toast.error("Delete failed. " + error.message, { autoClose: false });
}
};
render() { render() {
return ( return (
<> <>
@ -46,7 +56,10 @@ class CoursesPage extends React.Component {
Add Course Add Course
</button> </button>
<CourseList courses={this.props.courses} /> <CourseList
courses={this.props.courses}
onDeleteClick={this.handleDeleteCourse}
/>
</> </>
)} )}
</> </>
@ -83,7 +96,8 @@ function mapDispatchToProps(dispatch) {
actions: bindActionCreators( actions: bindActionCreators(
{ {
loadCourses: courseActions.loadCourses, loadCourses: courseActions.loadCourses,
loadAuthors: authorActions.loadAuthors loadAuthors: authorActions.loadAuthors,
deleteCourse: courseActions.deleteCourse
}, },
dispatch dispatch
) )

View File

@ -5,3 +5,10 @@ export const CREATE_COURSE_SUCCESS = "CREATE_COURSE_SUCCESS";
export const UPDATE_COURSE_SUCCESS = "UPDATE_COURSE_SUCCESS"; export const UPDATE_COURSE_SUCCESS = "UPDATE_COURSE_SUCCESS";
export const BEGIN_API_CALL = "BEGIN_API_CALL"; export const BEGIN_API_CALL = "BEGIN_API_CALL";
export const API_CALL_ERROR = "API_CALL_ERROR"; export const API_CALL_ERROR = "API_CALL_ERROR";
// By convention, actions that end in "_SUCCESS" are assumed to have been the result of a completed
// API call. But since we're doing an optimistic delete, we're hiding loading state.
// So this action name deliberately omits the "_SUCCESS" suffix.
// If it had one, our apiCallsInProgress counter would be decremented below zero
// because we're not incrementing the number of apiCallInProgress when the delete request begins.
export const DELETE_COURSE_OPTIMISTIC = "DELETE_COURSE_OPTIMISTIC";

View File

@ -14,6 +14,10 @@ export function updateCourseSuccess(course) {
return { type: types.UPDATE_COURSE_SUCCESS, course }; return { type: types.UPDATE_COURSE_SUCCESS, course };
} }
export function deleteCourseOptimistic(course) {
return { type: types.DELETE_COURSE_OPTIMISTIC, course };
}
export function loadCourses() { export function loadCourses() {
return function (dispatch) { return function (dispatch) {
dispatch(beginApiCall()); dispatch(beginApiCall());
@ -46,3 +50,12 @@ export function saveCourse(course) {
}); });
}; };
} }
export function deleteCourse(course) {
return function (dispatch) {
// Doing optimistic delete, so not dispatching begin/end api call
// actions, or apiCallError action since we're not showing the loading status for this.
dispatch(deleteCourseOptimistic(course));
return courseApi.deleteCourse(course.id);
};
}

View File

@ -14,6 +14,9 @@ export default function courseReducer(state = initialState.courses, action) {
course.id === action.course.id ? action.course : course course.id === action.course.id ? action.course : course
); );
case types.DELETE_COURSE_OPTIMISTIC:
return state.filter((course) => course.id !== action.course.id);
default: default:
return state; return state;
} }