├── 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 |
7 | 8 |
, 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 | 11 | ); -------------------------------------------------------------------------------- /01 Implementation/src/components/loadButton/loadButton.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './loadButton.css'; 3 | 4 | export const LoadButton = (props) => ( 5 | 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 |
29 | 30 | 31 |
, 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 |
33 | 37 |
38 | 39 |
40 |
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 |
36 | 40 |
41 | 42 |
43 |
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 |
33 | 34 |
, 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 |
72 | 73 | + 74 |
, 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 | --------------------------------------------------------------------------------