diff --git a/src/components/courses/CourseList.js b/src/components/courses/CourseList.js index 40cfe04..7ae652b 100644 --- a/src/components/courses/CourseList.js +++ b/src/components/courses/CourseList.js @@ -2,7 +2,7 @@ import React from "react"; import PropTypes from "prop-types"; import { Link } from "react-router-dom"; -const CourseList = ({ courses }) => ( +const CourseList = ({ courses, onDeleteClick }) => ( @@ -10,6 +10,7 @@ const CourseList = ({ courses }) => ( + @@ -29,6 +30,14 @@ const CourseList = ({ courses }) => ( + ); })} @@ -37,7 +46,8 @@ const CourseList = ({ courses }) => ( ); CourseList.propTypes = { - courses: PropTypes.array.isRequired + courses: PropTypes.array.isRequired, + onDeleteClick: PropTypes.func.isRequired }; export default CourseList; diff --git a/src/components/courses/CoursesPage.js b/src/components/courses/CoursesPage.js index 0b7cf5c..6e73725 100644 --- a/src/components/courses/CoursesPage.js +++ b/src/components/courses/CoursesPage.js @@ -7,6 +7,7 @@ import { bindActionCreators } from "redux"; import CourseList from "./CourseList"; import { Redirect } from "react-router-dom"; import Spinner from "../common/Spinner"; +import { toast } from "react-toastify"; class CoursesPage extends React.Component { 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() { return ( <> @@ -46,7 +56,10 @@ class CoursesPage extends React.Component { Add Course - + )} @@ -83,7 +96,8 @@ function mapDispatchToProps(dispatch) { actions: bindActionCreators( { loadCourses: courseActions.loadCourses, - loadAuthors: authorActions.loadAuthors + loadAuthors: authorActions.loadAuthors, + deleteCourse: courseActions.deleteCourse }, dispatch ) diff --git a/src/redux/actions/actionTypes.js b/src/redux/actions/actionTypes.js index 4fe8b31..612614e 100644 --- a/src/redux/actions/actionTypes.js +++ b/src/redux/actions/actionTypes.js @@ -5,3 +5,10 @@ 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"; + +// 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"; diff --git a/src/redux/actions/courseActions.js b/src/redux/actions/courseActions.js index be5f5a3..b59760c 100644 --- a/src/redux/actions/courseActions.js +++ b/src/redux/actions/courseActions.js @@ -14,6 +14,10 @@ export function updateCourseSuccess(course) { return { type: types.UPDATE_COURSE_SUCCESS, course }; } +export function deleteCourseOptimistic(course) { + return { type: types.DELETE_COURSE_OPTIMISTIC, course }; +} + export function loadCourses() { return function (dispatch) { 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); + }; +} diff --git a/src/redux/reducers/courseReducer.js b/src/redux/reducers/courseReducer.js index c1bf7f9..dcdbae5 100644 --- a/src/redux/reducers/courseReducer.js +++ b/src/redux/reducers/courseReducer.js @@ -14,6 +14,9 @@ export default function courseReducer(state = initialState.courses, action) { course.id === action.course.id ? action.course : course ); + case types.DELETE_COURSE_OPTIMISTIC: + return state.filter((course) => course.id !== action.course.id); + default: return state; }
Title Author Category
{course.authorName} {course.category} + +