Share via


SPFx: Using Office UI-Fabric Shimmer instead of traditional Loading or Progress indicators in

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. 

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

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

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

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.

// Add this import statement    
import {sp} from '@pnp/sp';    
     
// Over OnInit method to initialize pnpjs    
export default class CustomShimmerWebPart extends BaseClientSideWebPart<ICustomShimmerWebPartProps> {    
     
  public onInit(): Promise<void>{    
    sp.setup({    
      spfxContext: this.context    
    });    
    return Promise.resolve();    
  }    
 // .... rest of the code ommited    
}

Step 6 : Remove all the contents from src/webparts/customShimmer/components/CustomShimmer.module.scss file and replce with the following,

// Changed the import statement to load styles from Fabric React Package    
@import '~office-ui-fabric-react/dist/sass/_References.scss';    
     
.customShimmer {    
  .container {    
    max-width: 700px;    
    margin: 0px auto;    
    box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);    
  }    
     
  .row {    
    @include ms-Grid-row;    
    @include ms-fontColor-white;    
    background-color: $ms-color-themeDark;    
    padding: 20px;    
  }    
     
  .column {    
    @include ms-Grid-col;    
    @include ms-lg10;    
    @include ms-xl8;    
    @include ms-xlPush2;    
    @include ms-lgPush1;    
  }    
     
  .siteRow{    
    @include ms-Grid-row;    
    @include ms-fontColor-black;    
    width: 350px;    
  }    
     
  .imgColumn{    
    @include ms-Grid-col;    
    @include ms-lg2;    
    height: 40px;    
    width: 40px;    
  }    
     
  .titleColumn{    
    @include ms-Grid-col;    
    @include ms-lg10;    
    @include ms-font-l;    
    line-height: 40px;    
    height: 40px;    
  }    
     
  .subTitle {    
    @include ms-font-l;    
    @include ms-fontColor-white;    
  }    
}

Step 7 : Change the code in src/webparts/customShimmer/components/CustomShimmer.tsx, as shown below.

import * as React from 'react';     
import styles from './CustomShimmer.module.scss';    
import { ICustomShimmerProps } from './ICustomShimmerProps';    
import { escape } from '@microsoft/sp-lodash-subset';    
     
import { Shimmer, ShimmerElementsGroup, ShimmerElementType } from 'office-ui-fabric-react/lib/Shimmer';    
import {sp, SearchQuery, SearchResults} from '@pnp/sp';     
     
export interface ICustomShimmerState{    
  loaded: boolean;    
  sites: any[];    
}    
     
export default  class CustomShimmer extends React.Component<ICustomShimmerProps, ICustomShimmerState> {    
  public constructor(props:ICustomShimmerProps, state:ICustomShimmerState){    
    super(props);    
    this.state = {    
      loaded: false,    
      sites:[    
        {Title:"Site 1"},    
        {Title:"Site 2"},    
        {Title:"Site 3"},    
        {Title:"Site 4"},    
        {Title:"Site 5"},    
      ]    
    };    
  }    
     
  public render(): React.ReactElement<ICustomShimmerProps> {    
    const elements = this.state.sites.map((val,index) => {    
      return <div style={{padding:"10px", background:"white"}}>    
      <Shimmer    
        customElementsGroup={this._getElementsForSiteListing()}    
        isDataLoaded={this.state.loaded}>    
            <div className={ styles.siteRow }>    
            <div className={ styles.imgColumn }>    
              <img src={val.SiteLogo} height={40} width={40}/>    
            </div>    
            <div className={ styles.titleColumn }>    
              {val.Title}    
            </div>    
          </div>    
      </Shimmer>    
      </div>;    
    });    
         
    return (<div className={ styles.customShimmer }>    
      <div className={ styles.container }>    
        <div className={ styles.row }>    
          <div className={ styles.column }>    
     
            {elements}    
     
          </div>    
        </div>    
      </div>    
    </div>);    
  }    
     
  private _getElementsForSiteListing= (): JSX.Element => {    
    return (    
      <div    
        style={{ display: 'flex' }}     
      >    
        <ShimmerElementsGroup    
          shimmerElements={[    
            { type: ShimmerElementType.line, width: 40, height: 40 },    
            { type: ShimmerElementType.gap, width: 10, height: 40 }    
          ]}    
        />    
        <ShimmerElementsGroup    
          flexWrap={true}    
          shimmerElements={[    
            { type: ShimmerElementType.gap, width: 370, height: 10 },    
            { type: ShimmerElementType.line, width: 370, height: 10 },    
            { type: ShimmerElementType.gap, width: 370, height: 10 }    
          ]}    
        />    
      </div>    
    );    
  }    
     
  public componentDidMount()    
  {    
    // Sometime PnPjs is very fast, that we cannot see the shimmer.    
    // So let's put a 5 seconds delay to see the Shimmer effect    
    setTimeout(() => { sp.search({    
      SelectProperties: ["Title","SiteLogo"],    
      Querytext: `contentclass:STS_Site AND WebTemplate:'Group'`,    
      RowLimit: 5    
    }).then(w => {    
      console.dir(w);    
      this.setState({    
        sites:w.PrimarySearchResults,    
        loaded:true   
      });    
    });}, 5000);    
         
  }    
}

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,

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.