SPFx - The Modern Way To Show Loading/Progress Indicators Using Office-UI Fabric Shimmer

This article explains how to use Shimmer React component from Office UI Fabric React to show loading placeholders while we fetch the data to be shown.

Introduction

 
With the wide usage of client-side development, loading data asynchronously is the standard way to fetch the data from a data source. While the data is being fetched, a loading message, a spinner or a progress bar is shown to keep the user engaged and make them aware that data is still loading.
 
With SPFx web parts also, we have many spinners and loading indicators which are being used while the data is being read. Shimmer effects are the latest and more intuitive which are replacing the standard spinners and loading indicators.
 

What is Shimmer?

 
Shimmer is a loading effect which shows a placeholder that resembles the same structure when the actual data is loaded. This is very user-friendly as the user gets an impression of where to look for specific information, and gets a structural view before the data is loaded
 
For example, if we are trying to show the list of sites within an SPFx web part, you could see the behavior of a Shimmer effect below.
 
SPFx - The Modern Way To Show Loading/Progress Indicators Using Office-UI Fabric Shimmer
 
Here, the Shimmer shows placeholders for the site logo and title for 5 items. So, before the data loads the users will know what type of data they are going to see, which make a better user experience.
 
Below are the steps which explain how to use the Office-UI-Fabric React Shimmer component.
 

Create the SPFx project

 
Step 1 
 
Run yeoman SharePoint Framework generator to create the project
  1. yo @microsoft/sharepoint 
Step 2 
 
Provide the values for the project as prompted by the yeoman generator. Below are the values which I provided for the example solution
  • What is your solution name? (using-react-shimmer)
  • Which baseline packages do you want to target for your component(s)? SharePoint Online only (latest)
  • Where do you want to place the files? (Use arrow keys) Use the current folder
  • Do you want to allow the tenant admin the choice of being able to deploy the solution to all sites immediately without running any feature deployment or adding apps in sites? (y/N) N
  •  Will the components in the solution require permissions to access web APIs that are unique and not shared with other components in the tenant? (y/N) N
  • Which type of client-side component to create? (Use arrow keys) WebPart
  • What is your Web part name? (HelloWorld) CustomShimmer
  • What is your Web part description? (Shimmer description)
  • Which framework would you like to use? React
Step 3
 
Install PnPjs, for fetching the list of sites using Search
  1. npm install @pnp/pnpjs --save 
Step 4 
 
As we will be using Office UI React package, we can remove/uninstall the sp-office-ui-fabric-core package
  1. npm uninstall @microsoft/sp-office-ui-fabric-core --save 
This step is optional. However, I would recommend uninstalling this package to reduce the bundle size of our SPFx components. We also need to change the import statements to use the styles from Fabric react package instead of @microsoft/sp-office-ui-fabric-core
 
Step 5 
 
Open the src/webparts/customShimmer/CustomShimmerWebpart.ts and make the following changes.
  1. // Add this import statement    
  2. import {sp} from '@pnp/sp';    
  3.     
  4. // Over OnInit method to initialize pnpjs    
  5. export default class CustomShimmerWebPart extends BaseClientSideWebPart<ICustomShimmerWebPartProps> {    
  6.     
  7.   public onInit(): Promise<void>{    
  8.     sp.setup({    
  9.       spfxContext: this.context    
  10.     });    
  11.     return Promise.resolve();    
  12.   }    
  13.  // .... rest of the code ommited    
  14. }    
Step 6 
 
Remove all the contents from src/webparts/customShimmer/components/CustomShimmer.module.scss file and replce with the following,
  1. // Changed the import statement to load styles from Fabric React Package    
  2. @import '~office-ui-fabric-react/dist/sass/_References.scss';    
  3.     
  4. .customShimmer {    
  5.   .container {    
  6.     max-width: 700px;    
  7.     margin: 0px auto;    
  8.     box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);    
  9.   }    
  10.     
  11.   .row {    
  12.     @include ms-Grid-row;    
  13.     @include ms-fontColor-white;    
  14.     background-color: $ms-color-themeDark;    
  15.     padding: 20px;    
  16.   }    
  17.     
  18.   .column {    
  19.     @include ms-Grid-col;    
  20.     @include ms-lg10;    
  21.     @include ms-xl8;    
  22.     @include ms-xlPush2;    
  23.     @include ms-lgPush1;    
  24.   }    
  25.     
  26.   .siteRow{    
  27.     @include ms-Grid-row;    
  28.     @include ms-fontColor-black;    
  29.     width: 350px;    
  30.   }    
  31.     
  32.   .imgColumn{    
  33.     @include ms-Grid-col;    
  34.     @include ms-lg2;    
  35.     height: 40px;    
  36.     width: 40px;    
  37.   }    
  38.     
  39.   .titleColumn{    
  40.     @include ms-Grid-col;    
  41.     @include ms-lg10;    
  42.     @include ms-font-l;    
  43.     line-height: 40px;    
  44.     height: 40px;    
  45.   }    
  46.     
  47.   .subTitle {    
  48.     @include ms-font-l;    
  49.     @include ms-fontColor-white;    
  50.   }    
  51. }     
Step 7 
 
Change the code in src/webparts/customShimmer/components/CustomShimmer.tsx, as shown below.
  1. import * as React from 'react';    
  2. import styles from './CustomShimmer.module.scss';    
  3. import { ICustomShimmerProps } from './ICustomShimmerProps';    
  4. import { escape } from '@microsoft/sp-lodash-subset';    
  5.     
  6. import { Shimmer, ShimmerElementsGroup, ShimmerElementType } from 'office-ui-fabric-react/lib/Shimmer';    
  7. import {sp, SearchQuery, SearchResults} from '@pnp/sp';    
  8.     
  9. export interface ICustomShimmerState{    
  10.   loaded: boolean;    
  11.   sites: any[];    
  12. }    
  13.     
  14. export default class CustomShimmer extends React.Component<ICustomShimmerProps, ICustomShimmerState> {    
  15.   public constructor(props:ICustomShimmerProps, state:ICustomShimmerState){    
  16.     super(props);    
  17.     this.state = {    
  18.       loaded: false,    
  19.       sites:[    
  20.         {Title:"Site 1"},    
  21.         {Title:"Site 2"},    
  22.         {Title:"Site 3"},    
  23.         {Title:"Site 4"},    
  24.         {Title:"Site 5"},    
  25.       ]    
  26.     };    
  27.   }    
  28.     
  29.   public render(): React.ReactElement<ICustomShimmerProps> {    
  30.     const elements = this.state.sites.map((val,index) => {    
  31.       return <div style={{padding:"10px", background:"white"}}>    
  32.       <Shimmer    
  33.         customElementsGroup={this._getElementsForSiteListing()}    
  34.         isDataLoaded={this.state.loaded}>    
  35.             <div className={ styles.siteRow }>    
  36.             <div className={ styles.imgColumn }>    
  37.               <img src={val.SiteLogo} height={40} width={40}/>    
  38.             </div>    
  39.             <div className={ styles.titleColumn }>    
  40.               {val.Title}    
  41.             </div>    
  42.           </div>    
  43.       </Shimmer>    
  44.       </div>;    
  45.     });    
  46.         
  47.     return (<div className={ styles.customShimmer }>    
  48.       <div className={ styles.container }>    
  49.         <div className={ styles.row }>    
  50.           <div className={ styles.column }>    
  51.     
  52.             {elements}    
  53.     
  54.           </div>    
  55.         </div>    
  56.       </div>    
  57.     </div>);    
  58.   }    
  59.     
  60.   private _getElementsForSiteListing= (): JSX.Element => {    
  61.     return (    
  62.       <div    
  63.         style={{ display: 'flex' }}    
  64.       >    
  65.         <ShimmerElementsGroup    
  66.           shimmerElements={[    
  67.             { type: ShimmerElementType.line, width: 40, height: 40 },    
  68.             { type: ShimmerElementType.gap, width: 10, height: 40 }    
  69.           ]}    
  70.         />    
  71.         <ShimmerElementsGroup    
  72.           flexWrap={true}    
  73.           shimmerElements={[    
  74.             { type: ShimmerElementType.gap, width: 370, height: 10 },    
  75.             { type: ShimmerElementType.line, width: 370, height: 10 },    
  76.             { type: ShimmerElementType.gap, width: 370, height: 10 }    
  77.           ]}    
  78.         />    
  79.       </div>    
  80.     );    
  81.   }    
  82.     
  83.   public componentDidMount()    
  84.   {    
  85.     // Sometime PnPjs is very fast, that we cannot see the shimmer.    
  86.     // So let's put a 5 seconds delay to see the Shimmer effect    
  87.     setTimeout(() => { sp.search({    
  88.       SelectProperties: ["Title","SiteLogo"],    
  89.       Querytext: `contentclass:STS_Site AND WebTemplate:'Group'`,    
  90.       RowLimit: 5    
  91.     }).then(w => {    
  92.       console.dir(w);    
  93.       this.setState({    
  94.         sites:w.PrimarySearchResults,    
  95.         loaded:true    
  96.       });    
  97.     });}, 5000);    
  98.         
  99.   }    
  100. }     
We can use the isDataLoaded property of the Shimmer component to show the actual data when it is loaded.
 
Step 8
 
Save all the files, and run the solution to test on a SharePoint workbench,
  1. gulp serve 
This should now show a Shimmer effect, before loading the data.
 

Conclusion

 
Shimmers are the modern way of showing a loading or progress indicators and this article walks you through the steps to show a Fabric React Shimmer component within your SPFx solution.
 
The SPFx solution used in this article is available on GitHub for reference.