├── 00 Start
├── src
│ ├── common
│ │ └── components
│ │ │ └── table
│ │ │ ├── index.js
│ │ │ ├── table.js
│ │ │ └── table.css
│ ├── components
│ │ ├── userTable
│ │ │ ├── index.js
│ │ │ ├── header.js
│ │ │ ├── row.js
│ │ │ └── table.js
│ │ ├── loadButton
│ │ │ ├── index.js
│ │ │ ├── loadButton.js
│ │ │ └── loadButton.css
│ │ └── index.js
│ ├── app.css
│ ├── index.js
│ ├── api
│ │ ├── userAPI.js
│ │ └── fetch.js
│ └── app.js
├── public
│ └── index.html
└── package.json
├── 01 Implementation
├── src
│ ├── common
│ │ └── components
│ │ │ └── table
│ │ │ ├── index.js
│ │ │ ├── table.js
│ │ │ └── table.css
│ ├── components
│ │ ├── userTable
│ │ │ ├── index.js
│ │ │ ├── header.js
│ │ │ ├── row.js
│ │ │ └── table.js
│ │ ├── loadButton
│ │ │ ├── index.js
│ │ │ ├── loadButton.js
│ │ │ └── loadButton.css
│ │ └── index.js
│ ├── app.css
│ ├── api
│ │ ├── userAPI.js
│ │ └── fetch.js
│ ├── index.js
│ └── app.js
├── public
│ └── index.html
├── package.json
└── Readme.md
├── .gitignore
├── README.md
└── LICENSE
/00 Start/src/common/components/table/index.js:
--------------------------------------------------------------------------------
1 | export * from './table'
--------------------------------------------------------------------------------
/00 Start/src/components/userTable/index.js:
--------------------------------------------------------------------------------
1 | export * from './table';
--------------------------------------------------------------------------------
/00 Start/src/components/loadButton/index.js:
--------------------------------------------------------------------------------
1 | export * from './loadButton';
--------------------------------------------------------------------------------
/01 Implementation/src/common/components/table/index.js:
--------------------------------------------------------------------------------
1 | export * from './table'
--------------------------------------------------------------------------------
/01 Implementation/src/components/userTable/index.js:
--------------------------------------------------------------------------------
1 | export * from './table';
--------------------------------------------------------------------------------
/01 Implementation/src/components/loadButton/index.js:
--------------------------------------------------------------------------------
1 | export * from './loadButton';
--------------------------------------------------------------------------------
/00 Start/src/components/index.js:
--------------------------------------------------------------------------------
1 | export * from './userTable';
2 | export * from './loadButton';
--------------------------------------------------------------------------------
/01 Implementation/src/components/index.js:
--------------------------------------------------------------------------------
1 | export * from './userTable';
2 | export * from './loadButton';
--------------------------------------------------------------------------------
/00 Start/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/01 Implementation/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | dist/
4 | typings/
5 | *.orig
6 | .idea/
7 | */src/**/*.js.map
8 | *.log
9 | **/.vscode/
10 | package-lock.json
11 | desktop.ini
12 |
--------------------------------------------------------------------------------
/00 Start/src/components/userTable/header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const Header = (props) => (
4 |
5 | Id
6 | Name
7 | Email
8 |
9 | );
--------------------------------------------------------------------------------
/01 Implementation/src/components/userTable/header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const Header = (props) => (
4 |
5 | Id
6 | Name
7 | Email
8 |
9 | );
--------------------------------------------------------------------------------
/00 Start/src/app.css:
--------------------------------------------------------------------------------
1 | .tables {
2 | display: flex;
3 | flex-direction: row;
4 | flex-wrap: nowrap;
5 | }
6 |
7 | .tables > div {
8 | flex-basis: 50%;
9 | margin-left: 1rem;
10 | margin-right: 1rem;
11 | }
--------------------------------------------------------------------------------
/01 Implementation/src/app.css:
--------------------------------------------------------------------------------
1 | .tables {
2 | display: flex;
3 | flex-direction: row;
4 | flex-wrap: nowrap;
5 | }
6 |
7 | .tables > div {
8 | flex-basis: 50%;
9 | margin-left: 1rem;
10 | margin-right: 1rem;
11 | }
--------------------------------------------------------------------------------
/00 Start/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 | import { App } from './app';
4 |
5 | render(
6 | ,
9 | document.getElementById('root'));
10 |
--------------------------------------------------------------------------------
/00 Start/src/api/userAPI.js:
--------------------------------------------------------------------------------
1 | import { fetchWithDelay } from './fetch';
2 | const url = 'https://jsonplaceholder.typicode.com/users';
3 |
4 | const fetchUsers = () => fetchWithDelay(url);
5 |
6 | export const userAPI = {
7 | fetchUsers,
8 | };
--------------------------------------------------------------------------------
/01 Implementation/src/api/userAPI.js:
--------------------------------------------------------------------------------
1 | import { fetchWithDelay } from './fetch';
2 | const url = 'https://jsonplaceholder.typicode.com/users';
3 |
4 | const fetchUsers = () => fetchWithDelay(url);
5 |
6 | export const userAPI = {
7 | fetchUsers,
8 | };
--------------------------------------------------------------------------------
/00 Start/src/components/loadButton/loadButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './loadButton.css';
3 |
4 | export const LoadButton = (props) => (
5 |
9 | {props.title}
10 |
11 | );
--------------------------------------------------------------------------------
/01 Implementation/src/components/loadButton/loadButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './loadButton.css';
3 |
4 | export const LoadButton = (props) => (
5 |
9 | {props.title}
10 |
11 | );
--------------------------------------------------------------------------------
/00 Start/src/api/fetch.js:
--------------------------------------------------------------------------------
1 | export const fetchWithDelay = (url) => {
2 | const promise = new Promise((resolve, reject) => {
3 | setTimeout(() => {
4 | resolve(fetch(url, {
5 | method: 'GET',
6 | })
7 | .then((response) => response.json()));
8 | }, 3000)
9 | });
10 |
11 | return promise;
12 | }
--------------------------------------------------------------------------------
/00 Start/src/components/userTable/row.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const Row = (user) => (
4 |
5 |
6 | {user.id}
7 |
8 |
9 | {user.name}
10 |
11 |
12 | {user.email}
13 |
14 |
15 | );
--------------------------------------------------------------------------------
/01 Implementation/src/api/fetch.js:
--------------------------------------------------------------------------------
1 | export const fetchWithDelay = (url) => {
2 | const promise = new Promise((resolve, reject) => {
3 | setTimeout(() => {
4 | resolve(fetch(url, {
5 | method: 'GET',
6 | })
7 | .then((response) => response.json()));
8 | }, 3000)
9 | });
10 |
11 | return promise;
12 | }
--------------------------------------------------------------------------------
/01 Implementation/src/components/userTable/row.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const Row = (user) => (
4 |
5 |
6 | {user.id}
7 |
8 |
9 | {user.name}
10 |
11 |
12 | {user.email}
13 |
14 |
15 | );
--------------------------------------------------------------------------------
/00 Start/src/components/userTable/table.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Table } from '../../common/components/table';
3 | import { Header } from './header';
4 | import { Row } from './row';
5 |
6 | export const UserTable = (props) => (
7 |
13 | );
--------------------------------------------------------------------------------
/01 Implementation/src/components/userTable/table.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Table } from '../../common/components/table';
3 | import { Header } from './header';
4 | import { Row } from './row';
5 |
6 | export const UserTable = (props) => (
7 |
13 | );
--------------------------------------------------------------------------------
/00 Start/src/common/components/table/table.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './table.css';
3 |
4 | export const Table = (props) => (
5 |
6 |
{props.title}
7 |
8 |
9 | {props.headerRender()}
10 |
11 |
12 | {props.items.map(props.rowRender)}
13 |
14 |
15 |
16 | );
17 |
--------------------------------------------------------------------------------
/01 Implementation/src/common/components/table/table.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './table.css';
3 |
4 | export const Table = (props) => (
5 |
6 |
{props.title}
7 |
8 |
9 | {props.headerRender()}
10 |
11 |
12 | {props.items.map(props.rowRender)}
13 |
14 |
15 |
16 | );
17 |
--------------------------------------------------------------------------------
/00 Start/src/common/components/table/table.css:
--------------------------------------------------------------------------------
1 | .title {
2 | color: #000;
3 | }
4 |
5 | .table {
6 | width: 100%;
7 | max-width: 100%;
8 | margin-bottom: 1rem;
9 | background-color: transparent;
10 | border-collapse: collapse;
11 | }
12 |
13 | .table thead th {
14 | vertical-align: bottom;
15 | border-bottom: 2px solid #248f4f;
16 | }
17 |
18 | .table td, .table th {
19 | text-align: inherit;
20 | padding: .75rem;
21 | vertical-align: top;
22 | border-top: 1px solid #248f4f;
23 | }
24 |
25 | .table tbody tr:nth-of-type(odd) {
26 | background-color: rgba(43, 173, 96, .05);
27 | }
--------------------------------------------------------------------------------
/01 Implementation/src/common/components/table/table.css:
--------------------------------------------------------------------------------
1 | .title {
2 | color: #000;
3 | }
4 |
5 | .table {
6 | width: 100%;
7 | max-width: 100%;
8 | margin-bottom: 1rem;
9 | background-color: transparent;
10 | border-collapse: collapse;
11 | }
12 |
13 | .table thead th {
14 | vertical-align: bottom;
15 | border-bottom: 2px solid #248f4f;
16 | }
17 |
18 | .table td, .table th {
19 | text-align: inherit;
20 | padding: .75rem;
21 | vertical-align: top;
22 | border-top: 1px solid #248f4f;
23 | }
24 |
25 | .table tbody tr:nth-of-type(odd) {
26 | background-color: rgba(43, 173, 96, .05);
27 | }
--------------------------------------------------------------------------------
/00 Start/src/components/loadButton/loadButton.css:
--------------------------------------------------------------------------------
1 | .load-button {
2 | cursor: pointer;
3 | margin: 1rem;
4 | color: #fff;
5 | text-shadow: 1px 1px 0 #888;
6 | background-color: #2BAD60;
7 | border-color: #14522d;
8 | display: inline-block;
9 | font-weight: 400;
10 | text-align: center;
11 | white-space: nowrap;
12 | vertical-align: middle;
13 | border: 1px solid transparent;
14 | padding: .375rem .75rem;
15 | font-size: 1rem;
16 | line-height: 1.5;
17 | border-radius: .25rem;
18 | outline: none;
19 | }
20 |
21 |
22 |
23 | .load-button:hover {
24 | background-color: #248f4f;
25 | border-color: #1e7b43;
26 | }
--------------------------------------------------------------------------------
/01 Implementation/src/components/loadButton/loadButton.css:
--------------------------------------------------------------------------------
1 | .load-button {
2 | cursor: pointer;
3 | margin: 1rem;
4 | color: #fff;
5 | text-shadow: 1px 1px 0 #888;
6 | background-color: #2BAD60;
7 | border-color: #14522d;
8 | display: inline-block;
9 | font-weight: 400;
10 | text-align: center;
11 | white-space: nowrap;
12 | vertical-align: middle;
13 | border: 1px solid transparent;
14 | padding: .375rem .75rem;
15 | font-size: 1rem;
16 | line-height: 1.5;
17 | border-radius: .25rem;
18 | outline: none;
19 | }
20 |
21 |
22 |
23 | .load-button:hover {
24 | background-color: #248f4f;
25 | border-color: #1e7b43;
26 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # blog-post-loading-indicator-demos
2 |
3 | Material from the [How to easily display a loading indicator on Http calls](https://www.basefactor.com/react-how-to-display-a-loading-indicator-on-fetch-calls).
4 |
5 | # About Basefactor + Lemoncode
6 |
7 | We are an innovating team of Javascript experts, passionate about turning your ideas into robust products.
8 |
9 | [Basefactor, consultancy by Lemoncode](http://www.basefactor.com) provides consultancy and coaching services.
10 |
11 | [Lemoncode](http://lemoncode.net/services/en/#en-home) provides training services.
12 |
13 | For the LATAM/Spanish audience we are running an Online Front End Master degree, more info: http://lemoncode.net/master-frontend
14 |
--------------------------------------------------------------------------------
/00 Start/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-promise-tracker-default-area-sample",
3 | "version": "0.0.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "16.8.6",
7 | "react-dom": "16.8.6",
8 | "prop-types": "15.7.2",
9 | "react-loader-spinner": "2.3.0",
10 | "react-promise-tracker": "2.0.0"
11 | },
12 | "scripts": {
13 | "start": "react-scripts start",
14 | "build": "react-scripts build",
15 | "test": "react-scripts test --env=jsdom",
16 | "eject": "react-scripts eject"
17 | },
18 | "devDependencies": {
19 | "react-scripts": "latest"
20 | },
21 | "browserslist": [
22 | ">0.2%",
23 | "not dead",
24 | "not ie <= 11",
25 | "not op_mini all"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/01 Implementation/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-promise-tracker-default-area-sample",
3 | "version": "0.0.0",
4 | "private": true,
5 | "dependencies": {
6 | "prop-types": "15.7.2",
7 | "react": "16.8.6",
8 | "react-dom": "16.8.6",
9 | "react-loader-spinner": "^2.3.0",
10 | "react-promise-tracker": "^2.0.0"
11 | },
12 | "scripts": {
13 | "start": "react-scripts start",
14 | "build": "react-scripts build",
15 | "test": "react-scripts test --env=jsdom",
16 | "eject": "react-scripts eject"
17 | },
18 | "devDependencies": {
19 | "react-scripts": "latest"
20 | },
21 | "browserslist": [
22 | ">0.2%",
23 | "not dead",
24 | "not ie <= 11",
25 | "not op_mini all"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/01 Implementation/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { render } from "react-dom";
3 | import { App } from "./app";
4 | import Loader from "react-loader-spinner";
5 | import { usePromiseTracker } from "react-promise-tracker";
6 |
7 | const LoadingIndicator = props => {
8 | const { promiseInProgress } = usePromiseTracker();
9 |
10 | return (
11 | promiseInProgress && (
12 |
21 |
22 |
23 | )
24 | );
25 | };
26 |
27 | render(
28 | ,
32 | document.getElementById("root")
33 | );
34 |
--------------------------------------------------------------------------------
/00 Start/src/app.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { userAPI } from './api/userAPI';
3 | import { UserTable, LoadButton } from './components';
4 | import './app.css';
5 |
6 | export class App extends Component {
7 | constructor() {
8 | super();
9 |
10 | this.state = {
11 | users: [],
12 | };
13 |
14 | this.onLoadTables = this.onLoadTables.bind(this);
15 | }
16 |
17 | onLoadTables() {
18 | this.setState({
19 | users: [],
20 | });
21 |
22 | userAPI.fetchUsers()
23 | .then((users) => {
24 | this.setState({
25 | users,
26 | })
27 | });
28 | }
29 |
30 | render() {
31 | return (
32 |
41 | );
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/01 Implementation/src/app.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { userAPI } from './api/userAPI';
3 | import { UserTable, LoadButton } from './components';
4 | import './app.css';
5 | import { trackPromise } from 'react-promise-tracker';
6 |
7 | export class App extends Component {
8 | constructor() {
9 | super();
10 |
11 | this.state = {
12 | users: [],
13 | };
14 |
15 | this.onLoadTables = this.onLoadTables.bind(this);
16 | }
17 |
18 | onLoadTables() {
19 | this.setState({
20 | users: [],
21 | });
22 |
23 | trackPromise(
24 | userAPI.fetchUsers()
25 | .then((users) => {
26 | this.setState({
27 | users,
28 | })
29 | })
30 | );
31 | }
32 |
33 | render() {
34 | return (
35 |
44 | );
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Lemoncode
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/01 Implementation/Readme.md:
--------------------------------------------------------------------------------
1 | # Summary
2 |
3 | In this sample we are going to add a http loading indicator to our application, it will take care by itself to show/hide whenever there are tracked asynchronous calls in progress.
4 |
5 | # Steps
6 |
7 | Let's get started
8 |
9 | - First let's install the library react promise tracker
10 |
11 | ```bash
12 | npm install react-promise-tracker --save
13 | ```
14 |
15 | - Let's create a very basic loading indicator, let's call it _InnerLoadingIndicator_.
16 |
17 | _./src/index_
18 |
19 | ```diff
20 | import React, { Component } from 'react';
21 | import { render } from 'react-dom';
22 | import { App } from './app';
23 |
24 |
25 | + const LoadingIndicator = props => {
26 | + return (
27 | + Hey some async call in progress !
28 | + );
29 | + }
30 |
31 | render(
32 | ,
35 | document.getElementById('root'));
36 | ```
37 |
38 | - Now we need a way to know whether the loading indicator needs
39 | to be shown or not. React promise tracker implements a High Order Component that can report the current status to our loading indicator component.
40 |
41 | Let's start by importing the _promiseTrackerHoc_
42 |
43 | _./src/index_
44 |
45 | ```diff
46 | import React from 'react';
47 | + import { usePromiseTracker } from "react-promise-tracker";
48 | ```
49 |
50 | Use the _react-promise-tracker_ _usePromiseTracker_ hook.
51 |
52 | _./src/index_
53 |
54 | ```diff
55 | const LoadingIndicator = props => {
56 | + const { promiseInProgress } = usePromiseTracker();
57 |
58 | return (
59 | + promiseInProgress &&
60 | Hey some async call in progress !
61 | );
62 | }
63 | ```
64 |
65 | - We can now instantiate this component at our application entry point level
66 |
67 | _./src/index.ts_
68 |
69 | ```diff
70 | render(
71 | ,
75 | document.getElementById('root'));
76 | ```
77 |
78 | - Let's jump now to the place where we are making a fetch call, we will wrap
79 | the fetch call with a _trackPromise_ method.
80 |
81 | First we will add the import to the react promise tracker library
82 |
83 | _./app.js_
84 |
85 | ```diff
86 | import { UserTable, LoadButton } from './components';
87 | import './app.css';
88 | + import { trackPromise } from 'react-promise-tracker';
89 | ```
90 |
91 | Then we will wrap the fetch call with a _trackPromise_ method:
92 |
93 | _app.js_
94 |
95 | ```diff
96 | onLoadTables() {
97 | this.setState({
98 | users: [],
99 | });
100 |
101 | + trackPromise(
102 | userAPI.fetchUsers()
103 | .then((users) => {
104 | this.setState({
105 | users,
106 | })
107 | - });
108 | + }));
109 | }
110 | ```
111 |
112 | - now if we run the project we can see that the loading indicator is being shown when the asynchronous call is in progress. If we want to track any async call that returns a promise we just only need to wrap it with the _trackPromise_ method, _react-promise-tracker_.
113 |
114 | - To wrap up this sample let's make a more professional looking _loadingIndicator_. We will install a library called _react loader spinner_.
115 |
116 | ```bash
117 | npm install react-loader-spinner --save
118 | ```
119 |
120 | - Let's define the spinner, For the sake of simplicity we will define the styles inline.
121 |
122 | First le't add the corresponding import:
123 |
124 | _./src/index.js_
125 |
126 | ```diff
127 | import React, { Component } from 'react';
128 | import { render } from 'react-dom';
129 | import { App } from './app';
130 | + import Loader from 'react-loader-spinner';
131 | ```
132 |
133 | - Now let's pimp our loading indicator:
134 | - We will center the spinner to be displayedf.
135 | - We will add one of the spinners that read-loader-spinner offers.
136 |
137 | _./src/index.js_
138 |
139 | ```diff
140 | const LoadingIndicator = props => {
141 | const { promiseInProgress } = usePromiseTracker();
142 |
143 | return promiseInProgress &&
144 | - Hey some async call in progress ! ;
145 | +
154 | +
155 | +
156 | };
157 | ```
158 |
159 | - Now if we run the application we can see that we are getting a better looking loading indicator.
160 |
161 | ```bash
162 | npm start
163 | ```
164 |
165 | - If you want to learn more about React-Promise-Tracker you can check it's github page, it contains live samples plus documentation.
166 |
167 | https://github.com/Lemoncode/react-promise-tracker
168 |
169 |
170 |
171 |
--------------------------------------------------------------------------------