Optimistic delete
parent
5a85950b85
commit
82815b6d9a
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue