DEV Community

Cover image for Using React Shepherd to build a site tour
Megan Lee for LogRocket

Posted on • Originally published at blog.logrocket.com

Using React Shepherd to build a site tour

Written by Onuorah Bonaventure✏️

The fastest way to onboard first-time users on your website is by guiding them through some of the features your site has to offer. This phenomenon, also known as a site tour, ultimately shows users how to properly navigate through the site without facing any issues.

In this tutorial, I will show you how to use React Shepherd to easily implement a site tour on your website. React Shepherd is a lightweight wrapper library for Shepherd.js that allows us to create a walkthrough of a website — either parts of the site or the whole application — by using dialogs that represent steps that you create. At the end of this article, we will have a functioning site tour as shown in this video.

You can also check out the project code on GitHub. Let’s get started!

What is React Shepherd?

React Shepherd is an open source library that wraps around another open source library known as Shepherd.js. Its purpose is to make creating site tours easy, efficient, and aesthetic.

Essentially, React Shepherd provides two convenient ways to access methods and set objects for the tour without complexities: React Hooks and React Context. However, we will be focusing more on how to use React Context and the associated Provider component, which is the more convenient option.

Advantages of React Shepherd

The React Shepherd library comes with a number of advantages that make it a great tool to use for implementing a very powerful site tour in a React project:

  1. It’s highly customizable
  2. It has global state management built with Context
  3. It’s lightweight
  4. It’s highly accessible, providing actions with the user’s keyboard

We’ll see these advantages in action as we go through the step-by-step tutorial below.

How to use React Shepherd

The React Shepherd library basically accepts two props — steps and tourOptions. Although only steps is required, we should pass both props for the library work properly.

Generally, the steps prop expects an array of objects with the ShepherdOptionsWithType interface (or structure or type). Meanwhile, the tourOptions prop expects an object with the TourOptions interface.

The library expects that we pass these props to the provided ShepherdTour provider or to the useShepherdTour Hook, as shown in the sample below:

// With ShepherdTour 
  <ShepherdTour steps={newSteps} tourOptions={tourOptions}>
    {children}
  </ShepherdTour>

// With hook
  const tour = useShepherdTour({ tourOptions, steps: newSteps });
Enter fullscreen mode Exit fullscreen mode

To start the tour, we can use the tour.start() method. We also have other methods available in the Tour class to perform other actions, such as stopping the tour.

The ShepherdOptionsWithType, TourOptions, and Tour interfaces

Let’s explore React Shepherd’s interfaces. As mentioned previously, the required steps prop expects an array of objects with the ShepherdOptionsWithType interface, so let’s begin there.

ShepherdOptionsWithType

The ShepherdOptionsWithType interface typically helps us to create an object that contains the following 15 keys:

  • attachTo
  • arrow
  • beforeShowPromise
  • buttons
  • canClickTarget
  • classes
  • highlightClass
  • id
  • scrollTo
  • text
  • title
  • when
  • scrollToHandler
  • showOn
  • cancelIcon

We can combine these keys into array of objects and pass them to the steps prop. Let’s explore each one in more detail.

The attachTo key is used to bind a dialog (or step) to an element. When it's not provided, the dialog remains at the center of the screen. It expects an object with element and on keys, and can be used like this:

attachTo: { 'a html element selector', on: 'bottom'  }
Enter fullscreen mode Exit fullscreen mode

The on key can accept a lot more options, like:

'top'|'top-start'|'top-end'
'bottom'|'bottom-start'|'bottom-end'
'right'|'right-start'|'right-end'
'left'|'left-start'|'left-end'
Enter fullscreen mode Exit fullscreen mode

The arrow key accepts a boolean value and is used to show a tooltip on the dialog or to hide it. By default, the tooltip will be shown.

The beforeShowPromise key accepts a promise and is used to run a particular piece of code before other parts of the step will be shown. It can be used as like this:

        beforeShowPromise: function () {
          return new Promise(function (resolve: any) {
          // This can be any code
            setTimeout(function () {
              router.push("/about");
              resolve();
            }, 200);
          //
          });
        },
Enter fullscreen mode Exit fullscreen mode

The buttons key is used to add buttons to the modal. It accepts an array of objects with the following keys:

  • action is a function that exposes this, which allows us to call methods available in the tour
  • classes allow us to style the button with CSS
  • text allows us to pass the visible text of the button
  • type allows us to define how we expect the button to behave. type can be any of the following: 'back' | 'cancel' | 'next'

A buttons array can be defined as thus:

        buttons: [
          {
            classes: "shepherd-button-secondary",
            text: "Exit",
            type: "cancel",
          },
          {
            classes: "shepherd-button-primary",
            text: "Back",
            type: "back",
          },
          {
            classes: "shepherd-button-primary",
            text: "Next",
            type: "next",
          },
        ],
Enter fullscreen mode Exit fullscreen mode

You can even omit the type and define an action like this:

        buttons: [
          {
            classes: "shepherd-button-secondary",
            text: "Restart",
            action() {
              this.cancel();
              router.push("/dashboard");
              this.start();
            },
          },
          {
            classes: "shepherd-button-primary",
            text: "Done",
            type: "cancel",
          },
        ],
Enter fullscreen mode Exit fullscreen mode

The canClickTarget key is a boolean value. When set to false, it ensures that the targeted element where the dialog will be shown will not have a pointer event, meaning that it cannot be clicked at the time of the tour.

This key is most useful when certain components or elements contain links or sensitive parts of the app and you do not want users to mistakenly click on them during the tour.

The classes key accepts a string of classes separated by an empty space. It is used to style any part of the dialog.

The highlightClass key can be used to style the element we have attached the dialog (or step) to.

The id key is used internally to keep track of steps. Hence, it should be unique for all the steps.

The scrollTo key is used to make the dialog scroll to the attached element or not.

The text key accepts an array of strings and can used to pass paragraphs to the dialog. You would write the array of strings like so:

['first para', 'second para', 'third para']
Enter fullscreen mode Exit fullscreen mode

The title key, as the name suggests, is used to set the heading for the dialog.

The when key is a handy option that can be used to run functions when a dialog is shown or hidden. It basically accepts two keys: show and hide. You can use it like so:

    when: {
      show: () => {
        document.getElementById("nav-menu")?.click();
      },
      hide: () => {
        console.log('Run some code here');
      }
    }
Enter fullscreen mode Exit fullscreen mode

The scrollToHandler key accepts a function with a parameter that represents the element attached to the step. It can be used to define a custom scroll or other DOM method.

The showOn key accepts a function that can be used to run a piece of code that generally should return a boolean value. When it return a false value, then the step is not shown. It can be used like this:

    showOn?: (() => {
 if(someValue === anotherValue) {
    return true
    }
  });
Enter fullscreen mode Exit fullscreen mode

And finally, the cancelIcon key accepts an object comprised of enabled and label, which expect a string and a boolean value, respectively. This key is used to show or hide the icon.

We can combine these keys to form steps for our tour like this:

const steps = [{
        id: "tenth",
        title: "View most recent blog posts",
        text: ["View most recent blog posts at a glance"],
        attachTo: { element: "#blogs", on: "top" },
        scrollTo: true,
        arrow: true,
        classes: '.custom-design'
        buttons: [
          {
            classes: "shepherd-button-secondary",
            text: "Exit",
            type: "cancel",
          },
          {
            classes: "shepherd-button-primary",
            text: "Back",
            type: "back",
          },
          {
            classes: "shepherd-button-primary",
            text: "Next",
            type: "next",
          },
        ],
      },
      {
        id: "eleventh",
        title: "Navigate to any page from here",
        text: ["Navigate to the appropriate page by clicking on any"],
        attachTo: { element: "#sidemenu-about", on: "top" },
        scrollTo: true,
        canClickTarget: true,
        buttons: [
          {
            classes: "shepherd-button-secondary",
            text: "Restart",
            action() {
              this.cancel();
              router.push("/dashboard");
              this.start();
            },
          },
          {
            classes: "shepherd-button-primary",
            text: "Done",
            type: "cancel",
          },
        ],
        when: {
          show: () => {
            document.getElementById("sidemenu-about")?.click();
            localStorage.setItem("demo-done", JSON.stringify(true));
          },
          hide: () => {},
        },
        beforeShowPromise: function () {
          return new Promise(function (resolve: any) {
            setTimeout(function () {
              router.push("/about");
              resolve();
            }, 200);
          });
        },
      },
    ];
Enter fullscreen mode Exit fullscreen mode

The simple demo above would result in two tour steps, internally labeled with id keys of tenth and eleventh. See if you can determine how each step would appear and behave based on the configurations above. Then, let’s move on to discussing the next interface.

tourOptions

The tourOptions interface allows us to add more functionality to the whole tour. For instance, we can use confirmCancel to add a window.confirm modal when closing the modal by clicking the x icon which we can equally show or hide using the defaultStepOptions option.

Some of the options for this interface are as follows:

  1. confirmCancel: Accepts a boolean value and can be used to attach a confirmation before closing the whole tour
  2. confirmCancelMessage: Accepts a string and it is used to define the message that will be shown in the window.confirm modal
  3. defaultStepOptions: Accepts all the default options that we can pass to a step; note that enabling this will apply all the default options to all the steps
  4. exitOnEsc: Accepts a boolean value and is used to determine whether the esc key should close the tour or not. By default, the value is set to true
  5. keyboardNavigation: Accepts a boolean value and is used to determine whether the user is allowed to navigate through the tour using the left and right arrow keyboard keys. By default, the value is set to true
  6. tourName: Used to attach a name to the tour. It is generally used to generate a id for the tour.
  7. useModalOverlay: Accepts a boolean value. When it is set to true, the whole page will have a dark overlay

These properties can be used like so:

const tourOptions = {
  defaultStepOptions: {
    cancelIcon: {
      enabled: false,
    },
  },
  useModalOverlay: true,
  keyboardNavigation: true,
  exitOnEsc: true // And so on...
};
Enter fullscreen mode Exit fullscreen mode

Tour

The Tour class has bunch of methods we can use to perform specific actions, such as starting and canceling the tour. Some of the methods that exist in this class are:

  • addStep(): Accepts an object created with the ShepherdOptionsWithType interface and can be used to add a new step to the tour
  • addSteps(): Similar to addStep(); however, it allows us to to add an array of multiple objects created with the ShepherdOptionsWithType interface
  • back(): Used go back to a previous tour step
  • next(): Used to go to the next tour step
  • start(): Used to begin the entire tour

Other important methods to know include removeStep, which accepts a name as a parameter, and isActive(), which is used to indicate that a particular step is the active one. The complete list of methods can be found in the Shepherd.js documentation.

Project setup for our React Shepherd site tour

The next step in this tutorial is to implement the tour. To properly follow along, I have provided a modified version of a React project based on this free dashboard template. Download or clone this starter project containing the essential code needed to follow along.

Basically, by cloning or downloading this starter code, your folder structure should look similar to the one I have provided below:

// Folder structure
app
 ┣ assets
 ┃ ┗ images
 ┃ ┃ ┣ bg
 ┃ ┃ ┃ ┣ bg1.jpg
 ┃ ┃ ┃ ┣ bg2.jpg
 ┃ ┃ ┃ ┣ bg3.jpg
 ┃ ┃ ┃ ┗ bg4.jpg
 ┃ ┃ ┗ users
 ┃ ┃ ┃ ┣ user1.jpg
 ┃ ┃ ┃ ┣ user2.jpg
 ┃ ┃ ┃ ┣ user3.jpg
 ┃ ┃ ┃ ┣ user4.jpg
 ┃ ┃ ┃ ┗ user5.jpg
 ┣ components
 ┃ ┣ blog.tsx
 ┃ ┣ blogs.tsx
 ┃ ┣ feeds.tsx
 ┃ ┣ header.tsx
 ┃ ┣ layout.tsx
 ┃ ┣ projects-table.tsx
 ┃ ┣ sales-chart.tsx
 ┃ ┗ sidebar.tsx
 ┣ pages
 ┃ ┣ api
 ┃ ┃ ┗ hello.ts
 ┃ ┣ _app.tsx
 ┃ ┣ _document.tsx
 ┃ ┣ about.tsx
 ┃ ┣ dashboard.tsx
 ┃ ┗ index.tsx
 ┣ public
 ┃ ┣ favicon.ico
 ┃ ┣ next.svg
 ┃ ┗ vercel.svg
 ┣ styles
 ┃ ┣ scss
 ┃ ┃ ┣ layout
 ┃ ┃ ┃ ┣ _container.scss
 ┃ ┃ ┃ ┗ _sidebar.scss
 ┃ ┃ ┣ _variables.scss
 ┃ ┃ ┗ style.scss
 ┃ ┗ globals.css
 ┣ .eslintrc.json
 ┣ .gitignore
 ┣ next.config.mjs
 ┣ package-lock.json
 ┗ package.json
Enter fullscreen mode Exit fullscreen mode

Once you have the code, you can cd into the root folder of the project and run npm install or yarn install to install dependencies. After the dependencies have been added, you can start the project by running npm run dev or yarn dev, since this was actually set up using Next.js.

Adding steps to the React Shepherd site tour

In this section, we will set up each step of the site tour. First, let’s install the React Shepherd library using either of the commands below:

npm install react-shepherd
//OR
yarn add react-shepherd
Enter fullscreen mode Exit fullscreen mode

Next, we will add tour.tsx file in the components folder alongside components like blog.tsx. In the components/tour.tsx file, we will import necessary dependencies, including the styles required to make the dialogs look pretty:

// components/tour.tsx

import { useRouter } from "next/router";
import { PropsWithChildren, useContext, useEffect, useMemo } from "react";
import {
  ShepherdOptionsWithType,
  ShepherdTour,
  ShepherdTourContext,
} from "react-shepherd";
import { Button } from "reactstrap";
import "shepherd.js/dist/css/shepherd.css";
Enter fullscreen mode Exit fullscreen mode

Next, we will create the tourOption object like so:

// components/tour.tsx

const tourOptions = {
  defaultStepOptions: {
    cancelIcon: {
      enabled: false,
    },
  },
  useModalOverlay: true,
};
Enter fullscreen mode Exit fullscreen mode

After that, we will create a TourInstance component with the following code below the tourOptions object like so:

// components/tour.tsx

const TourInstance: React.FC<PropsWithChildren> = ({ children }) => {
  const tour = useContext(ShepherdTourContext);

  useEffect(() => {
    if (typeof window !== "undefined") {
      const demoDone = localStorage.getItem("demo-done")
        ? JSON.parse(localStorage.getItem("demo-done") || "")
        : null;
      if (demoDone) return;
      tour?.start();
    }
  }, [tour]);

  return <>{children}</>;
};
Enter fullscreen mode Exit fullscreen mode

In the code above, we extract the tour instance from the ShepherdTourContext. Then, in the useEffect Hook, we check if the window is defined before using the value of demo-done in the localStorage. This will prevent the tour from restarting when we’re done with the tour’s steps.

After that, we run the tour.start function. We also return the passed children into the DOM.

Next, we will create another component where we will pass the steps and the tourOptions to the ShepherdTour consumer. Our code will look similar to this:

// components/tour.tsx

const Tour: React.FC<PropsWithChildren> = ({ children }) => {
  const router = useRouter();
  const newSteps: ShepherdOptionsWithType[] = []

  return (
    <ShepherdTour steps={newSteps} tourOptions={tourOptions}>
      <TourInstance>{children}</TourInstance>
    </ShepherdTour>
  );
};
export default Tour;
Enter fullscreen mode Exit fullscreen mode

The next process is to define the steps. In total, we will have eleven steps, which will have different configurations:

// components/tour.tsx

  const newSteps: ShepherdOptionsWithType[] = useMemo(() => {
    return [
      {
        id: "first",
        title: "Welcome to App Dashboard",
        text: [
          "Let us take you on a journey to explore the most important features of our site",
        ],
        scrollTo: false,
        arrow: false,
        buttons: [
          {
            classes: "shepherd-button-secondary",
            text: "Exit",
            type: "cancel",
          },
          {
            classes: "shepherd-button-primary",
            text: "Next",
            type: "next",
          },
        ],
      },
Enter fullscreen mode Exit fullscreen mode

The first step defined above only has six configurations attached. It has been set up this way so that we can place it at the center of the page, since we don’t want it to be attached to any item. The properties scrollTo and arrowTo were set to false, and we added only two buttons — Exit and Next.

Here’s the second step:

      {
        id: "second",
        title: "App navigation bar",
        text: ["Explore how you can easily navigate through the app"],
        attachTo: { element: "#navigation", on: "bottom" },
        scrollTo: true,
        buttons: [
          {
            classes: "shepherd-button-secondary",
            text: "Exit",
            type: "cancel",
          },
          {
            classes: "shepherd-button-primary",
            text: "Back",
            type: "back",
          },
          {
            classes: "shepherd-button-primary",
            text: "Next",
            type: "next",
          },
        ],
      },
Enter fullscreen mode Exit fullscreen mode

The second dialog was attached to an element with an id of #navigation, which can be found in components/header.tsx. Notice how the on key has a value of bottom because we want the dialog to be positioned at the right of the element.

Also, the scrollTo property has now been set to true because we expect the dialog to be moved to the #navigation element. There are also three buttons now, adding a Back button to the Exit and Next buttons we already set up in the previous step.

Notice how the class provided by Shepherd.js was attached to the buttons. This will ensure we have very neat default styles when we finally use the code.

Let’s take a look at the third and fourth tour steps:

      {
        id: "third",
        title: "Navigate to the dashboard",
        text: ["Click here to quickly navigate to the dashboard index"],
        attachTo: { element: "#nav-dashboard", on: "bottom" },
        scrollTo: true,
        buttons: [
          {
            classes: "shepherd-button-secondary",
            text: "Exit",
            type: "cancel",
          },
          {
            classes: "shepherd-button-primary",
            text: "Back",
            type: "back",
          },
          {
            classes: "shepherd-button-primary",
            text: "Next",
            type: "next",
          },
        ],
      },
      {
        id: "fourth",
        title: "Navigate to about page",
        text: ["Click here to quickly navigate to the about page"],
        attachTo: { element: "#nav-about", on: "bottom" },
        scrollTo: true,
        buttons: [
          {
            classes: "shepherd-button-secondary",
            text: "Exit",
            type: "cancel",
          },
          {
            classes: "shepherd-button-primary",
            text: "Back",
            type: "back",
          },
          {
            classes: "shepherd-button-primary",
            text: "Next",
            type: "next",
          },
        ],
      },
Enter fullscreen mode Exit fullscreen mode

The third and fourth steps are similar to the second step, except that they have been attached to different elements. These steps have an id of #nav-dashboard and #nav-about, respectively, which can still be found in the components/header.tsx file.

Next, we’ll look at the fifth and sixth steps:

      {
        id: "fifth",
        title: "Open extra menu options",
        text: ["Click here to open extra menu options"],
        attachTo: { element: "#nav-menu", on: "right" },
        scrollTo: true,
        canClickTarget: true,
        buttons: [
          {
            classes: "shepherd-button-secondary",
            text: "Exit",
            type: "cancel",
          },
          {
            classes: "shepherd-button-primary",
            text: "Back",
            type: "back",
          },
          {
            classes: "shepherd-button-primary",
            text: "Next",
            type: "next",
          },
        ],
        when: {
          show: () => {
            document.getElementById("nav-menu")?.click();
          },
          hide: () => {},
        },
      },
      {
        id: "sixth",
        title: "Open profile options",
        text: ["Click here to open profile options"],
        attachTo: { element: "#nav-profile", on: "left" },
        canClickTarget: true,
        scrollTo: true,
        buttons: [
          {
            classes: "shepherd-button-secondary",
            text: "Exit",
            type: "cancel",
          },
          {
            classes: "shepherd-button-primary",
            text: "Back",
            type: "back",
          },
          {
            classes: "shepherd-button-primary",
            text: "Next",
            type: "next",
          },
        ],
        when: {
          show: () => {
            document.getElementById("nav-profile")?.click();
          },
          hide: () => {
            console.log("hide step");
          },
        },
      },
Enter fullscreen mode Exit fullscreen mode

The fifth and sixth steps are similar to our previous steps. However, we attached them to different elements using their id keys — #nav-menu and #nav-profile, respectively.

We have also added two extra properties to the steps — canClickTarget and when. This is so we automatically click on the items being discussed when we are on their respective steps.

We’ll look at the code for the seventh, eighth, ninth, and tenth steps all together:

      {
        id: "seventh",
        title: "Sales overview",
        text: ["Graph that contains visual details about the sales prohgress"],
        attachTo: { element: "#sales-chart", on: "bottom" },
        scrollTo: true,
        buttons: [
          {
            classes: "shepherd-button-secondary",
            text: "Exit",
            type: "cancel",
          },
          {
            classes: "shepherd-button-primary",
            text: "Back",
            type: "back",
          },
          {
            classes: "shepherd-button-primary",
            text: "Next",
            type: "next",
          },
        ],
      },
      {
        id: "eighth",
        title: "Quickly find out what's going on",
        text: ["Updates on what is going on..."],
        attachTo: { element: "#feed", on: "left" },
        scrollTo: true,
        buttons: [
          {
            classes: "shepherd-button-secondary",
            text: "Exit",
            type: "cancel",
          },
          {
            classes: "shepherd-button-primary",
            text: "Back",
            type: "back",
          },
          {
            classes: "shepherd-button-primary",
            text: "Next",
            type: "next",
          },
        ],
      },
      {
        id: "ninth",
        title: "Overview of projects listings",
        text: ["Summary of projects carried out"],
        attachTo: { element: "#projects-listing", on: "top" },
        scrollTo: true,
        buttons: [
          {
            classes: "shepherd-button-secondary",
            text: "Exit",
            type: "cancel",
          },
          {
            classes: "shepherd-button-primary",
            text: "Back",
            type: "back",
          },
          {
            classes: "shepherd-button-primary",
            text: "Next",
            type: "next",
          },
        ],
      },
      {
        id: "tenth",
        title: "View most recent blog posts",
        text: ["View most recent blog posts at a glance"],
        attachTo: { element: "#blogs", on: "top" },
        scrollTo: true,
        buttons: [
          {
            classes: "shepherd-button-secondary",
            text: "Exit",
            type: "cancel",
          },
          {
            classes: "shepherd-button-primary",
            text: "Back",
            type: "back",
          },
          {
            classes: "shepherd-button-primary",
            text: "Next",
            type: "next",
          },
        ],
      },
Enter fullscreen mode Exit fullscreen mode

Steps 7–10 are also similar to the ones above, except for the elements they are attached to:

  • #sales-chart, which can be found in the components/sales-chart.tsx file
  • #feeds, which can be found in the components/feeds.tsx file
  • #projects-listing, which can be found in the components/projects-listing.tsx file
  • #blogs, which can be found in the components/blogs.tsx file

The eleventh step is the most interesting of all:

      {
        id: "eleventh",
        title: "Navigate to any page from here",
        text: ["Navigate to the appropriate page by clicking on any"],
        attachTo: { element: "#sidemenu-about", on: "top" },
        scrollTo: true,
        canClickTarget: true,
        buttons: [
          {
            classes: "shepherd-button-secondary",
            text: "Restart",
            action() {
              this.cancel();
              router.push("/dashboard");
              this.start();
            },
          },
          {
            classes: "shepherd-button-primary",
            text: "Done",
            type: "cancel",
          },
        ],
        when: {
          show: () => {
            document.getElementById("sidemenu-about")?.click();
            localStorage.setItem("demo-done", JSON.stringify(true));
          },
          hide: () => {},
        },
        beforeShowPromise: function () {
          return new Promise(function (resolve: any) {
            setTimeout(function () {
              router.push("/about");
              resolve();
            }, 200);
          });
        },
      },
    ];
  }, []);
Enter fullscreen mode Exit fullscreen mode

What makes this step so interesting is that we have to route to a different page on the website without actually stopping the tour. We also have to restart the tour right from the other page. Initially, we start off in the /dashboard page before finally navigating to the /about page.

This can be handled using a series of steps. First, we use the show method inside the when property to click on the #sidemenu-about on the sidebar. Then, we set the demo-done value to true inside the localStorage.

Next, we implemented the beforeShowPromise function, which we use to ensure that we navigate to the /about page before we even show the dialog. We added a custom Restart button to the step, which implements an action that we use to cancel the existing tour, navigate back to the dashboard page, and start a new tour.

Implementing our custom React Shepherd tour

Now that we have defined our steps, we can use our tour in our project. We start by first wrapping the entry to our app — pages/_app.tsx — with the the Tour component we have created.

One thing to notice is the dynamic import we have done for the Tour, which was done to fix any issues that might arise while using our project with Next.js. Our pages/_app.tsx should now look like this:

// pages/_app.tsx

import { Layout } from "@/components/layout";
import "@/styles/globals.css";
import "@/styles/scss/style.scss";
import type { AppProps } from "next/app";
import dynamic from "next/dynamic";
import "shepherd.js/dist/css/shepherd.css";

const Tour = dynamic(() => import("../components/tour"), { ssr: false });

export default function App({ Component, pageProps }: AppProps) {
  return (
    <Tour>
      <Layout>
        <Component {...pageProps} />
      </Layout>
    </Tour>
  );
}
Enter fullscreen mode Exit fullscreen mode

By now, our app should have a demo that functions as shown in the YouTube video above. Our site tour now works with the React Provider–React Context method, but we can augment it further — for example, by adding a Start button that allows a user restart the tour at any given time.

Let’s see how to add this button now. We have to open components/sidebar.tsx file, add the handleStartTour function, and add the Start tour button to the DOM like so:

// components/sidebar.tsx

import Link from "next/link";
import { useRouter } from "next/router";
import { useContext } from "react";
import { ShepherdTourContext } from "react-shepherd";
import { Button, Nav, NavItem } from "reactstrap";

const navigation = [
  {
    title: "Dashboard",
    href: "/dashboard",
    icon: "bi bi-speedometer2",
    id: "sidemenu-dashboard",
  },
  {
    title: "About",
    href: "/about",
    icon: "bi bi-people",
    id: "sidemenu-about",
  },
];
export const Sidebar = () => {
  const location = useRouter();

  // Import the context and define the tour
  const tour = useContext(ShepherdTourContext);

  const showMobilemenu = () => {
    if (typeof window !== "undefined") {
      document?.getElementById("sidebarArea")?.classList.toggle("showSidebar");
    }
  };

  const handleStartTour = () => {
    if (typeof window !== "undefined") {
      localStorage.removeItem("demo-done");
      // Restart the tour any time
      tour?.start();
    }
  };

  return (
    <div className="p-3">
      <div className="d-flex align-items-center">
        <Link
          href={"/"}
          className="nav-link border py-2 px-4 bg-dark text-white"
        >
          App LOGO
        </Link>
        <span className="ms-auto d-lg-none">
          <Button
            close
            size="sm"
            className="ms-auto d-lg-none"
            onClick={() => showMobilemenu()}
          ></Button>
        </span>
      </div>
      <div className="pt-4 mt-2">
        <Nav vertical className="sidebarNav">
          {navigation.map((navi, index) => (
            <NavItem key={index} className="sidenav-bg" id={navi.id}>
              <Link
                href={navi.href}
                className={
                  location.pathname === navi.href
                    ? "text-primary nav-link py-3"
                    : "nav-link text-secondary py-3"
                }
              >
                <i className={navi.icon}></i>
                <span className="ms-3 d-inline-block">{navi.title}</span>
              </Link>
            </NavItem>
          ))}
          <Button color="primary" onClick={handleStartTour} className="mt-4">
            Start tour
          </Button>
        </Nav>
      </div>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

As I mentioned earlier, React Shepherd provides both React Hooks and React Context with its Provider component for accessing methods and setting objects for the tour. We focused on using the Context method in this tutorial.

However, let’s still take a look at using the Hook provided by the React Shepherd library. This is totally optional, but may be of help to you as you work with React Shepherd.

This implementation is quite simple — we just have to open the components/tour.tsx file and add the following code to it:

// components/tour.tsx  

const tour = useShepherdTour({ tourOptions, steps: newSteps });

  return (
    <>
      <Button
        onClick={tour.start}
        style={{
          position: "fixed",
          bottom: "50vh",
          left: "24px",
          minWidth: 200,
        }}
      >
        Start tour with hook
      </Button>
      {children}
    </>
  );
  // return (
  //   <ShepherdTour steps={newSteps} tourOptions={tourOptions}>
  //     <TourInstance>{children}</TourInstance>
  //   </ShepherdTour>
  // );
Enter fullscreen mode Exit fullscreen mode

Conclusion

React Shepherd is a really great tool for site creating site tours in a React app. In this tutorial, we have used it to create a site of a dashboard site created with React.

There are many other libraries available for creating site tours, including React Tour and React Joyride, vue-tour for Vue projects, the Driver.js library, and more. Among these, React Shepherd stands out due to the elegant UI it provides along with its out-of-the-box, easy-to-use React Context implementation.

The React Tour package makes it easy to actually make any part of the app “tour-able,” meaning that we can add tour steps to any part of the app from a single point of truth. This often requires extra steps in other packages.

To review and play around with the code we implemented above, check out this GitHub repository. If you have any questions, you’re welcome to comment below.

Top comments (0)