Show a loader while navigating on a single-page application (SPA) with Sitecore JSS and React

Sitecore JSS with React is still quite popular. It has the highest npm stats at the moment. If you're building a single-page application (SPA) with Sitecore JSS and React, you may have noticed that if a page is slow, there's no feedback. You could improve performance with the Hybrid Placeholder, but sometimes you need an easier solution. In this blog I will explain how to show a loader while navigating between pages on a SPA.

Implementation

If you install Sitecore JSS with React through the sitecore-jss-react npm package it will contain the RouteHandler.js file. For this blog we'll use the version that was released with Sitecore JSS 20.0.0 and 21.0.0 since that's the newest version and both of those versions are the same.

The RouteHandler.js file is the file that is used to navigate between pages. It uses the Layout Service to fetch the data of the new page and render that. This description is at the top of the file:

// Dynamic route handler for Sitecore items.
// Because JSS app routes are defined in Sitecore, traditional static React routing isn't enough -
// we need to be able to load dynamic route data from Sitecore after the client side route changes.
// So react-router delegates all route rendering to this handler, which attempts to get the right
// route data from Sitecore - and if none exists, renders the not found component.

The updateLayoutData function is where the data of the new page is fetched. So if we want to show a loader we should change the loading state in this function. The RouteHandler is still a Class Component so we don't use hooks in this example.

First we'll add a loading state in the constructor:

class RouteHandler extends React.Component {
  constructor(props) {
    super(props);
    this.state = { loading: false };

    // tell i18next to sync its current language with the route language
    this.updateLanguage();
  }

Next we'll update the state in the updateLayoutData function:

updateLayoutData() {
  let sitecoreRoutePath = this.props.route.match.params.sitecoreRoute || '/';
  if (!sitecoreRoutePath.startsWith('/')) {
    sitecoreRoutePath = `/${sitecoreRoutePath}`;
  }

  const language = this.getLanguage();

  // instantiate the dictionary service.
  const layoutServiceInstance = layoutServiceFactory.create();

  this.setState({ loading: true });
  // get the route data for the new route
  layoutServiceInstance.fetchLayoutData(sitecoreRoutePath, language).then((routeData) => {
    this.props.updateSitecoreContext(routeData);
    this.setState({ loading: false });
  });
}

And finally we'll show a top loader component above the Layout component:

return (
  <React.Fragment>
    {/* {this.state.loading && <div>Loading...</div>} */}
    <TopLoader
      backgroundColor="#eee6ff"
      show={this.state.loading}
      fixed={true}
      color="#0000e4"
      duration={600}
      thickness={5}
    />
    <Layout route={layoutData.route} />
  </React.Fragment>
);

Result

You can find the updated RouteHandler.js file here: https://gist.github.com/jbreuer/8b656e6b784b9a5d15295318cb92cd9e

Once you've implemented a loader on your Sitecore JSS with React website it will look like this:

Sitecore JSS React SPA Loader

Remarks

In RouteHandler.js file there is also a comment about showing a "Loading" component:

// Don't render anything if the route data or dictionary data is not fully loaded yet.
// This is a good place for a "Loading" component, if one is needed.
if (!layoutData.route) {
    return null;
}

I did some tests, but it seems layoutData.route is never empty when you navigate between pages. So it doesn't show the loader like my example does.

Umbraco

This blog is focused on Sitecore because a standard SPA React frontend is available for it that includes routing. For Umbraco Heartcore there are Client Libraries, but those are not for a specific frontend like React. For our own Umbraco headless implementation we've created a custom SPA React frontend which works similar to the one that Sitecore provides. There you can also change the loading state before fetching content of the page and update it again when the fetching is done.