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 { 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";
|
||||||
|
|
||||||
class CoursesPage extends React.Component {
|
class CoursesPage extends React.Component {
|
||||||
state = {
|
state = {
|
||||||
|
@ -33,16 +34,21 @@ class CoursesPage extends React.Component {
|
||||||
<>
|
<>
|
||||||
{this.state.redirectToAddCoursePage && <Redirect to="/course" />}
|
{this.state.redirectToAddCoursePage && <Redirect to="/course" />}
|
||||||
<h2>Courses</h2>
|
<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
|
<CourseList courses={this.props.courses} />
|
||||||
style={{ marginBottom: 20 }}
|
</>
|
||||||
className="btn btn-primary add-course"
|
)}
|
||||||
onClick={() => this.setState({ redirectToAddCoursePage: true })}
|
|
||||||
>
|
|
||||||
Add Course
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<CourseList courses={this.props.courses} />
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -51,7 +57,8 @@ class CoursesPage extends React.Component {
|
||||||
CoursesPage.propTypes = {
|
CoursesPage.propTypes = {
|
||||||
courses: PropTypes.array.isRequired,
|
courses: PropTypes.array.isRequired,
|
||||||
authors: PropTypes.array.isRequired,
|
authors: PropTypes.array.isRequired,
|
||||||
actions: PropTypes.object.isRequired
|
actions: PropTypes.object.isRequired,
|
||||||
|
loading: PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state) {
|
||||||
|
@ -66,7 +73,8 @@ function mapStateToProps(state) {
|
||||||
authorName: author.name
|
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 { bindActionCreators } from "redux";
|
||||||
import CourseForm from "./CourseForm";
|
import CourseForm from "./CourseForm";
|
||||||
import { newCourse } from "../../../tools/mockData";
|
import { newCourse } from "../../../tools/mockData";
|
||||||
|
import Spinner from "../common/Spinner";
|
||||||
|
|
||||||
function ManageCoursePage({ courses, authors, actions, history, ...props }) {
|
function ManageCoursePage({ courses, authors, actions, history, ...props }) {
|
||||||
const [course, setCourse] = useState({ ...props.course });
|
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
|
<CourseForm
|
||||||
course={course}
|
course={course}
|
||||||
errors={errors}
|
errors={errors}
|
||||||
|
|
|
@ -3,3 +3,4 @@ export const LOAD_COURSES_SUCCESS = "LOAD_COURSES_SUCCESS";
|
||||||
export const LOAD_AUTHORS_SUCCESS = "LOAD_AUTHORS_SUCCESS";
|
export const LOAD_AUTHORS_SUCCESS = "LOAD_AUTHORS_SUCCESS";
|
||||||
export const CREATE_COURSE_SUCCESS = "CREATE_COURSE_SUCCESS";
|
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";
|
||||||
|
|
|
@ -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 types from "./actionTypes";
|
||||||
import * as authorApi from "../../api/authorApi";
|
import * as authorApi from "../../api/authorApi";
|
||||||
|
import { beginApiCall } from "./apiStatusActions";
|
||||||
|
|
||||||
function loadAuthorsSuccess(authors) {
|
function loadAuthorsSuccess(authors) {
|
||||||
return { type: types.LOAD_AUTHORS_SUCCESS, authors };
|
return { type: types.LOAD_AUTHORS_SUCCESS, authors };
|
||||||
|
@ -7,6 +8,7 @@ function loadAuthorsSuccess(authors) {
|
||||||
|
|
||||||
export function loadAuthors() {
|
export function loadAuthors() {
|
||||||
return function (dispatch) {
|
return function (dispatch) {
|
||||||
|
dispatch(beginApiCall());
|
||||||
return authorApi
|
return authorApi
|
||||||
.getAuthors()
|
.getAuthors()
|
||||||
.then((authors) => {
|
.then((authors) => {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import * as types from "./actionTypes";
|
import * as types from "./actionTypes";
|
||||||
import * as courseApi from "../../api/courseApi";
|
import * as courseApi from "../../api/courseApi";
|
||||||
|
import { beginApiCall } from "./apiStatusActions";
|
||||||
|
|
||||||
function loadCoursesSuccess(courses) {
|
function loadCoursesSuccess(courses) {
|
||||||
return { type: types.LOAD_COURSES_SUCCESS, courses };
|
return { type: types.LOAD_COURSES_SUCCESS, courses };
|
||||||
|
@ -15,6 +16,7 @@ export function updateCourseSuccess(course) {
|
||||||
|
|
||||||
export function loadCourses() {
|
export function loadCourses() {
|
||||||
return function (dispatch) {
|
return function (dispatch) {
|
||||||
|
dispatch(beginApiCall());
|
||||||
return courseApi
|
return courseApi
|
||||||
.getCourses()
|
.getCourses()
|
||||||
.then((courses) => {
|
.then((courses) => {
|
||||||
|
@ -29,6 +31,7 @@ export function loadCourses() {
|
||||||
export function saveCourse(course) {
|
export function saveCourse(course) {
|
||||||
//eslint-disable-next-line no-unused-vars
|
//eslint-disable-next-line no-unused-vars
|
||||||
return function (dispatch, getState) {
|
return function (dispatch, getState) {
|
||||||
|
dispatch(beginApiCall());
|
||||||
return courseApi
|
return courseApi
|
||||||
.saveCourse(course)
|
.saveCourse(course)
|
||||||
.then((savedCourse) => {
|
.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 { combineReducers } from "redux";
|
||||||
import courseReducer from "./courseReducer";
|
import courseReducer from "./courseReducer";
|
||||||
import authorReducer from "./authorReducer";
|
import authorReducer from "./authorReducer";
|
||||||
|
import apiStatusReducer from "./apiStatusReducer";
|
||||||
|
|
||||||
const rootReducer = combineReducers({
|
const rootReducer = combineReducers({
|
||||||
courses: courseReducer,
|
courses: courseReducer,
|
||||||
authors: authorReducer
|
authors: authorReducer,
|
||||||
|
apiCallsInProgress: apiStatusReducer
|
||||||
});
|
});
|
||||||
|
|
||||||
export default rootReducer;
|
export default rootReducer;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
export default {
|
export default {
|
||||||
courses: [],
|
courses: [],
|
||||||
authors: []
|
authors: [],
|
||||||
|
apiCallsInProgress: 0
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue