Skip to content

Instantly share code, notes, and snippets.

Last active November 1, 2022 16:47
Show Gist options
  • Save jbreuer/8b656e6b784b9a5d15295318cb92cd9e to your computer and use it in GitHub Desktop.
Save jbreuer/8b656e6b784b9a5d15295318cb92cd9e to your computer and use it in GitHub Desktop.
Show a loader while navigating on a single-page application (SPA) with Sitecore JSS and React
import React from 'react';
import i18n from 'i18next';
import Helmet from 'react-helmet';
import TopLoader from 'react-top-loader';
import { isEditorActive, withSitecoreContext } from '@sitecore-jss/sitecore-jss-react';
import { layoutServiceFactory } from './lib/layout-service-factory';
import config from './temp/config';
import Layout from './Layout';
import NotFound from './NotFound';
/* eslint-disable no-console */
// 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.
class RouteHandler extends React.Component {
constructor(props) {
this.state = { loading: false };
// tell i18next to sync its current language with the route language
componentDidMount() {
// If we are not using SSR we have to load layout data
if (!this.props.isSSR) {
* Loads route data from Sitecore Layout Service into state.routeData
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.setState({ loading: false });
getLanguage() {
return (
this.props.route.match.params.lang ||
this.props.sitecoreContext.language ||
* Updates the current app language to match the route data.
updateLanguage() {
const newLanguage = this.getLanguage();
if (i18n.language !== newLanguage) {
componentDidUpdate(previousProps) {
const existingRoute = previousProps.route.match.url;
const newRoute = this.props.route.match.url;
// don't change state (refetch route data) if the route has not changed
if (existingRoute === newRoute) {
// if in Sitecore editor - force reload instead of route data update
// avoids confusing Sitecore's editing JS
if (isEditorActive()) {
render() {
const layoutData = this.props.sitecoreContext;
// Note: this is client-side only 404 handling. Server-side 404 handling is the responsibility
// of the server being used (i.e. node-headless-ssr-proxy and Sitecore intergrated rendering know how to send 404 status codes).
// `route` is null in case if route is not found
if (layoutData.route === null) {
return (
<title>{i18n.t('Page not found')}</title>
<NotFound context={layoutData} />
// 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;
// Render the app's root structural layout
return (
{/* {this.state.loading && <div>Loading...</div>} */}
<Layout route={layoutData.route} />
export default withSitecoreContext({ updatable: true })(RouteHandler);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment