Spinner when wait for async actions
parent
1e70e1de6e
commit
8b95aec904
|
@ -0,0 +1,42 @@
|
|||
/* via https://projects.lukehaas.me/css-loaders/ */
|
||||
.loader,
|
||||
.loader:after {
|
||||
border-radius: 50%;
|
||||
width: 10em;
|
||||
height: 10em;
|
||||
}
|
||||
.loader {
|
||||
margin: 60px auto;
|
||||
font-size: 10px;
|
||||
position: relative;
|
||||
text-indent: -9999em;
|
||||
border-top: 1.1em solid rgba(255, 151, 0, 0.2);
|
||||
border-right: 1.1em solid rgba(255, 151, 0, 0.2);
|
||||
border-bottom: 1.1em solid rgba(255, 151, 0, 0.2);
|
||||
border-left: 1.1em solid #ff9700;
|
||||
-webkit-transform: translateZ(0);
|
||||
-ms-transform: translateZ(0);
|
||||
transform: translateZ(0);
|
||||
-webkit-animation: load8 1.1s infinite linear;
|
||||
animation: load8 1.1s infinite linear;
|
||||
}
|
||||
@-webkit-keyframes load8 {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@keyframes load8 {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
import React from "react";
|
||||
import "./Spinner.css";
|
||||
|
||||
const Spinner = () => {
|
||||
return <div className="loader">Loading...</div>;
|
||||
};
|
||||
|
||||
export default Spinner;
|
|
@ -6,6 +6,7 @@ import PropTypes from "prop-types";
|
|||
import { bindActionCreators } from "redux";
|
||||
import CourseList from "./CourseList";
|
||||
import { Redirect } from "react-router-dom";
|
||||
import Spinner from "../common/Spinner";
|
||||
|
||||
class CoursesPage extends React.Component {
|
||||
state = {
|
||||
|
@ -33,16 +34,21 @@ class CoursesPage extends React.Component {
|
|||
<>
|
||||
{this.state.redirectToAddCoursePage && <Redirect to="/course" />}
|
||||
<h2>Courses</h2>
|
||||
{this.props.loading ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
<>
|
||||
<button
|
||||
style={{ marginBottom: 20 }}
|
||||
className="btn btn-primary add-course"
|
||||
onClick={() => this.setState({ redirectToAddCoursePage: true })}
|
||||
>
|
||||
Add Course
|
||||
</button>
|
||||
|
||||
<button
|
||||
style={{ marginBottom: 20 }}
|
||||
className="btn btn-primary add-course"
|
||||
onClick={() => this.setState({ redirectToAddCoursePage: true })}
|
||||
>
|
||||
Add Course
|
||||
</button>
|
||||
|
||||
<CourseList courses={this.props.courses} />
|
||||
<CourseList courses={this.props.courses} />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -51,7 +57,8 @@ class CoursesPage extends React.Component {
|
|||
CoursesPage.propTypes = {
|
||||
courses: PropTypes.array.isRequired,
|
||||
authors: PropTypes.array.isRequired,
|
||||
actions: PropTypes.object.isRequired
|
||||
actions: PropTypes.object.isRequired,
|
||||
loading: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
|
@ -66,7 +73,8 @@ function mapStateToProps(state) {
|
|||
authorName: author.name
|
||||
};
|
||||
}),
|
||||
authors: state.authors
|
||||
authors: state.authors,
|
||||
loading: state.apiCallsInProgress > 0
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import PropTypes from "prop-types";
|
|||
import { bindActionCreators } from "redux";
|
||||
import CourseForm from "./CourseForm";
|
||||
import { newCourse } from "../../../tools/mockData";
|
||||
import Spinner from "../common/Spinner";
|
||||
|
||||
function ManageCoursePage({ courses, authors, actions, history, ...props }) {
|
||||
const [course, setCourse] = useState({ ...props.course });
|
||||
|
@ -42,7 +43,9 @@ function ManageCoursePage({ courses, authors, actions, history, ...props }) {
|
|||
});
|
||||
}
|
||||
|
||||
return (
|
||||
return authors.length === 0 || course.length === 0 ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
<CourseForm
|
||||
course={course}
|
||||
errors={errors}
|
||||
|
|
|
@ -3,3 +3,4 @@ export const LOAD_COURSES_SUCCESS = "LOAD_COURSES_SUCCESS";
|
|||
export const LOAD_AUTHORS_SUCCESS = "LOAD_AUTHORS_SUCCESS";
|
||||
export const CREATE_COURSE_SUCCESS = "CREATE_COURSE_SUCCESS";
|
||||
export const UPDATE_COURSE_SUCCESS = "UPDATE_COURSE_SUCCESS";
|
||||
export const BEGIN_API_CALL = "BEGIN_API_CALL";
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
import * as types from "./actionTypes";
|
||||
|
||||
export function beginApiCall() {
|
||||
return { type: types.BEGIN_API_CALL };
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import * as types from "./actionTypes";
|
||||
import * as authorApi from "../../api/authorApi";
|
||||
import { beginApiCall } from "./apiStatusActions";
|
||||
|
||||
function loadAuthorsSuccess(authors) {
|
||||
return { type: types.LOAD_AUTHORS_SUCCESS, authors };
|
||||
|
@ -7,6 +8,7 @@ function loadAuthorsSuccess(authors) {
|
|||
|
||||
export function loadAuthors() {
|
||||
return function (dispatch) {
|
||||
dispatch(beginApiCall());
|
||||
return authorApi
|
||||
.getAuthors()
|
||||
.then((authors) => {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import * as types from "./actionTypes";
|
||||
import * as courseApi from "../../api/courseApi";
|
||||
import { beginApiCall } from "./apiStatusActions";
|
||||
|
||||
function loadCoursesSuccess(courses) {
|
||||
return { type: types.LOAD_COURSES_SUCCESS, courses };
|
||||
|
@ -15,6 +16,7 @@ export function updateCourseSuccess(course) {
|
|||
|
||||
export function loadCourses() {
|
||||
return function (dispatch) {
|
||||
dispatch(beginApiCall());
|
||||
return courseApi
|
||||
.getCourses()
|
||||
.then((courses) => {
|
||||
|
@ -29,6 +31,7 @@ export function loadCourses() {
|
|||
export function saveCourse(course) {
|
||||
//eslint-disable-next-line no-unused-vars
|
||||
return function (dispatch, getState) {
|
||||
dispatch(beginApiCall());
|
||||
return courseApi
|
||||
.saveCourse(course)
|
||||
.then((savedCourse) => {
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
import * as types from "../actions/actionTypes";
|
||||
import initialState from "./initialState";
|
||||
|
||||
function actionTypeEndsInSuccess(type) {
|
||||
return type.substring(type.length - 8) === "_SUCCESS";
|
||||
}
|
||||
|
||||
export default function apiCallStatusReducer(
|
||||
state = initialState.apiCallsInProgress,
|
||||
action
|
||||
) {
|
||||
if (action.type == types.BEGIN_API_CALL) {
|
||||
return state + 1;
|
||||
} else if (actionTypeEndsInSuccess(action.type)) {
|
||||
return state - 1;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
|
@ -1,10 +1,12 @@
|
|||
import { combineReducers } from "redux";
|
||||
import courseReducer from "./courseReducer";
|
||||
import authorReducer from "./authorReducer";
|
||||
import apiStatusReducer from "./apiStatusReducer";
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
courses: courseReducer,
|
||||
authors: authorReducer
|
||||
authors: authorReducer,
|
||||
apiCallsInProgress: apiStatusReducer
|
||||
});
|
||||
|
||||
export default rootReducer;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
export default {
|
||||
courses: [],
|
||||
authors: []
|
||||
authors: [],
|
||||
apiCallsInProgress: 0
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue