A Practical Guide To Product Tours In React Apps

A Practical Guide To Product Tours In React Apps

As stated on Appcues:

“Product tours — sometimes called product walkthroughs — introduce users to a new product and help them find their bearings.”

Usually, when it needs to showcase a new feature or complex UI functionality in a web app, the customer-success team would send a campaign email to all of its users. While this is a great way to create such awareness, some users might not have the opportunity to see the added feature; hence, the purpose of the email would be defeated.

A better way to increase user awareness of a particular feature in a web app is by integrating concise, self-explanatory UI tips, called product tours.

Product tours guide users to “a-ha” moments, or showcase high-value features that are being underused. Product tours can be powerful tools to introduce users to a new product and to help them find their bearings. They can draw attention to product launches, promo offers, and product sales.

But when done wrong, product tours can end up feeling like a backseat driver. And no one likes a backseat driver, do they?

In this tutorial, you’ll learn about what a product tour is and the types of product-tour packages in the React ecosystem, along with their pros and cons.

If you’re building customer-facing products using React, then you might be keen to implement this in your React application. By the end, we’ll have built a product tour for a simple shopping-cart UI using React Joyride.

We won’t go through React and JavaScript’s syntax basics, but you don’t have to be an expert in either of these languages to follow along.

Product Tour Guidelines

Product tours are a tricky aspect of web apps, requiring some user-experience expertise to drive results. I’d recommend going through Appcues’ tips for product tours. The following are a few guidelines to consider.

Never Lecture

Putting a lot of tours on a web page is tempting. But users are usually not keen on long introductory tutorials. They become anxious when they have to ingest a lot of information before being able to use a feature in the app.

Break It Down

Don’t teach everything. Focus on a single feature, and create a tour of two to three steps to showcase that feature. Show many small tours, rather than a single long tour. Prioritize their sequence.

Add Value

Do you enjoy taking your own tour? How about your teammates? Present the tour in such a way that users will understand. Showcase value, rather than stories.

Now that we know the value of product tours and seen some guidelines for building them, let’s cover some React libraries for product tours and learn how to use them.

There are only a few React-based libraries for implementing tours. Two of the most popular are React Tour and React Joyride.

React Tour

React Tour has around 1,600 stars on GitHub and is being actively developed. The best use case for React Tour is a simple product tour in which little customization is required. A demo is available.

How It Works

With React Tour, you pass the className selector and content for each step to the component. The library will render the tour’s user interface based on a button click, or after you’ve mounted the component. It’s simple for static pages and UIs:

 const steps = [
  {
    selector: '.first-tour',
    content: 'This is the content for the first tour.',
  },
  {
    selector: '.second-tour',
    content: 'Here is the content for the second Tour.',
  }
  // ...
]

Pros

  • React Tour is best for tours that need little customization.
  • It works well for static content and for dynamic content whose selector labels always exist in the UI.
  • Fans of styled-components might find it interesting because it has a hard dependency on styled-components.

Cons

  • If your project has no dependency on styled-components, then you might not find it easy to implement.
  • Your creativity will be limited because it doesn’t support customization.

React Joyride

The other main product-tour library is React Joyride, which has about 3,100 stars on GitHub and is also actively maintained.

How It Works

We pass the className as a target and the content. The state stores the tour. The Joyride component uses steps as props.

  state = {
    steps: [
      {
        target: '.my-first-step',
        content: 'This is my awesome feature!',
      },
      {
        target: '.my-other-step',
        content: 'This is another awesome feature!',
      },
      ...
    ]
  };

  render () {
    const { steps } = this.state;

    return (
      
...
); } }

Pros

  • Integrating React Joyride in a web app is less rigid than with React Tour, and it has no hard dependency on other libraries.
  • Events and actions are made available, which fosters customization.
  • It’s frequently improved.

Cons

  • The UI isn’t as elegant as React Tour’s.

Why React Joyride?

Product tours, especially for really big web apps, require customization, and that sets React Joyride apart from React Tour. The example project we’ll make demands some creativity and customization — hence, we’ll go with React Joyride.

Building A Simple Product Tour

First, we’ll build a simple React tour using the props available to us in React Joyride. Next, we’ll use the useReducer hook to automate the tour’s processes.

Clone the “standard-tour” branch in the GitHub repository, or use the web page of your choice, as long as you’re able to follow along.

Install the packages by running npm install.

To start the app, run npm run start.

We’ll be covering the following steps:

  • define the tour’s steps;
  • enable a skip option in each step;
  • change text labels on buttons and links;
  • customize styles like button colors and text alignment.

Then, we’ll add some custom features:

  • autostart the tour;
  • start the tour manually (i.e. with a link or button click);
  • hide the blinking beacon.

The props in React Joyride enable us to perform some basic functionality.

For this tutorial, we’ll build a product tour of the UI shown below:

Define The Tour’s Steps

To begin with, ensure that you’re targeting the particular classNames that will hold the content of the tour on the page — that is, according to whether you’ll be using your UI instead of the shopping-cart UI.

In the component folder, create a Tour.js file, and paste the following code into it. Also, ensure that the target classNames exist in your style sheet. Throughout this article, we’ll tweak the Tour.js component to suit the task at hand.

import React from "react";
import JoyRide from "react-joyride";
const TOUR_STEPS = [
  {
    target: ".tour-logo",
    content: "This is our tour’s logo",
  },
  {
    target: ".tour-cart",
    content:
      "View the cart you’ve added here",
  },
  {
    target: ".tour-contact",
    content: "Contact the developer",
  },
  {
    target: ".tour-policy",
    content: "We accept returns after 14 days max",
  },
];

What we’ve done is simply define our tour’s steps by targeting the classNames that will form the bedrock of our content (the text). The content property is where we define the text that we want to see when the tour starts.

Enable Skip Option in Each Step

A skip option is important in cases where a user isn’t interested in a particular tour. We can add this feature by setting the showSkipButton prop to true, which will skip the remaining steps. Also, the continuous prop comes in handy when we need to show the Next button in each step.

 const Tour = () => {
  return (
    <>
      <JoyRide steps={TOUR_STEPS} continuous={true} showSkipButton={true} />
    </>
  );
};

Change Text Labels On Buttons And Links

To change the text labels on either buttons or links, we’ll use the locale prop. The locale prop has two objects, last and skip. We specified our last tour as the End tour, while skip is the Close tour.

 const Tour = () => {
  return (
    <>
      <JoyRide
        steps={TOUR_STEPS}
        continuous={true}
        showSkipButton={true}
        locale={{
          last: "End tour",
          skip: "Close tour"
        }}
      />
    </>
  );
};

Customize Styles, Like Button Colors And Text Alignment

The default color of buttons is red, and text alignment is always set right. Let’s apply some custom styles to change button colors and align text properly.

We see in our code that the styles prop is an object. It has other objects with unique values, including:

  • tooltipContainer
    Its key is textAlign, and its value is left.
  • buttonNext
    Its key is backgroundColor, and its value is green.
  • buttonBack
    Its key is marginRight, and its value is 10px.
  • locale
    Its keys are last and skip, and its values are End Tour and Close Tour, respectively.
const Tour = () => {
  return (
    <>
      <JoyRide
        steps={TOUR_STEPS}
        continuous={true}
        showSkipButton={true}
        styles={{
          tooltipContainer: {
            textAlign: "left"
          },
          buttonNext: {
            backgroundColor: "green"
          },
          buttonBack: {
            marginRight: 10
          }
        }}
        locale={{
          last: "End tour",
          skip: "Close tour"
        }}
      />
    </>
  );
};

The library exposes some props to use on our elements in place of the default elements, some of which are:

  • beaconComponent
  • tooltipComponent

useReducer

We’ve seen how to create a product tour and how to customize it using the various props of Joyride.

The problem with props, however, is that, as your web app scales and you need more tours, you don’t just want to add steps and pass props to them. You want to be able to automate the process by ensuring that the process of managing tours is controlled by functions, and not merely props. Therefore, we’ll use useReducer to revamp the process of building tours.

In this segment, we are going to take control of the tour by using actions and events, made available by the library through a callback function.

To make this process feel less daunting, we’ll break this down into steps, enabling us to build the tour in chunks.

The complete source code is available, but I’d advise you to follow this guide, to understand how it works. All of our steps will be done in the Tour.js file in the components folder.

Define the Steps

import React from "react";
import JoyRide from "react-joyride";
const TOUR_STEPS = [
  {
    target: ".tour-logo",
    content: "This is our tour’s logo.",
  },
  {
    target: ".tour-cart",
    content:
      "View the cart you’ve added here",
  },
  {
    target: ".tour-contact",
    content: "Contact the developer",
  },
  {
    target: ".tour-policy",
    content: "We accept returns after 14 days max",
  },
];

In this first step, we define our steps by targeting the appropriate classNames and setting our content (text).

Define the Initial State

 const INITIAL_STATE = {
  run: false,
  continuous: true,
  loading: false,
  stepIndex: 0, // Make the component controlled
  steps: TOUR_STEPS,
  key: new Date(), // This field makes the tour to re-render when the tour is restarted
};

In this step, we define some important states, including:

  • Set the run field to false, to ensure that the tour doesn’t start automatically.
  • Set the continuous prop to true, because we want to show the button.
  • stepIndex is the index number, which is set to 0.
  • The steps field is set to the TOUR_STEPS that we declared in step 1.
  • The key field makes the tour re-render when the tour is restarted.

Manage The State With Reducer

 const reducer = (state = INITIAL_STATE, action) => {
  switch (action.type) {
    // start the tour
    case "START":
      return { ...state, run: true };
    // Reset to 0th step
    case "RESET":
      return { ...state, stepIndex: 0 };
    // Stop the tour
    case "STOP":
      return { ...state, run: false };
    // Update the steps for next / back button click
    case "NEXT_OR_PREV":
      return { ...state, ...action.payload };
    // Restart the tour - reset go to 1st step, restart create new tour
    case "RESTART":
      return {
        ...state,
        stepIndex: 0,
        run: true,
        loading: false,
        key: new Date()
      };
    default:
      return state;
  }
};

In this step, using a switch statement when case is START, we return the state and set the run field to true. Also, when case is RESET, we return the state and set stepIndex to 0. Next, when case is STOP, we set the run field to false, which will stop the tour. Lastly, when case is RESET, we restart the tour and create a new tour.

According to the events (start, stop, and reset), we’ve dispatched the proper state to manage the tour.

Listen to the Callback Changes and Dispatch State Changes

 import JoyRide, { ACTIONS, EVENTS, STATUS } from "react-joyride";
const callback = data => {
    const { action, index, type, status } = data;
    if (action === ACTIONS.CLOSE
                ||
       (status === STATUS.SKIPPED && tourState.run)
                ||
      status === STATUS.FINISHED
    ) {
      dispatch({ type: "STOP" });
    } else if (type === EVENTS.STEP_AFTER || type === EVENTS.TARGET_NOT_FOUND) {
      dispatch({
        type: "NEXT_OR_PREV",
        payload: { stepIndex: index + (action === ACTIONS.PREV ? -1 : 1) }
      });
    }
};

Using the exposed EVENTS, ACTIONS, and STATUS labels offered by React Joyride, we listen to the click events and then perform some conditional operations.

In this step, when the close or skip button is clicked, we close the tour. Otherwise, if the next or back button is clicked, we check whether the target element is active on the page. If the target element is active, then we go to that step. Otherwise, we find the next-step target and iterate.

Autostart the Tour With useEffect

useEffect(() => {
  if(!localStorage.getItem("tour"){
    dispatch({ type: "START"});
  }
}, []);

In this step, the tour is auto-started when the page loads or when the component is mounted, using the useEffect hook.

Trigger The Start Button

const startTour = () => {
  dispatch({ type: "RESTART" });
};

The function in this last step starts the tour when the start button is clicked, just in case the user wishes to view the tour again. Right now, our app is set up so that the tour will be shown every time the user refreshes the page.

Here’s the final code for the tour functionality in Tour.js:

import React, { useReducer, useEffect } from "react";
import JoyRide, { ACTIONS, EVENTS, STATUS } from "react-joyride";

// Define the steps
const TOUR_STEPS = [
  {
    target: ".tour-logo",
    content: "This is our tour’s logo.",
    disableBeacon: true,
  },
  {
    target: ".tour-cart",
    content:
      "View the cart you’ve added here",
  },
  {
    target: ".tour-contact",
    content: "Contact the developer",
  },
  {
    target: ".tour-policy",
    content: "We accept returns after 14 days max",
  },
];

// Define our state
const INITIAL_STATE = {
  key: new Date(),
  run: false,
  continuous: true,
  loading: false,
  stepIndex: 0,
  steps: TOUR_STEPS,
};

// Set up the reducer function
const reducer = (state = INITIAL_STATE, action) => {
  switch (action.type) {
    case "START":
      return { ...state, run: true };
    case "RESET":
      return { ...state, stepIndex: 0 };
    case "STOP":
      return { ...state, run: false };
    case "NEXT_OR_PREV":
      return { ...state, ...action.payload };
    case "RESTART":
      return {
        ...state,
        stepIndex: 0,
        run: true,
        loading: false,
        key: new Date(),
      };
    default:
      return state;
  }
};

// Define the Tour component
const Tour = () => {
  const [tourState, dispatch] = useReducer(reducer, INITIAL_STATE);
  useEffect(() => {
    if (!localStorage.getItem("tour")) {
      dispatch({ type: "START" });
    }
  }, []);
  const callback = (data) => {
    const { action, index, type, status } = data;
    if (
      action === ACTIONS.CLOSE ||
      (status === STATUS.SKIPPED && tourState.run) ||
      status === STATUS.FINISHED
    ) {
      dispatch({ type: "STOP" });
    } else if (type === EVENTS.STEP_AFTER || type === EVENTS.TARGET_NOT_FOUND) {
      dispatch({
        type: "NEXT_OR_PREV",
        payload: { stepIndex: index + (action === ACTIONS.PREV ? -1 : 1) },
      });
    }
  };
  const startTour = () => {
    dispatch({ type: "RESTART" });
  };
  return (
    <>
      <button className="btn btn-primary" onClick={startTour}>
        Start Tour
      </button>
      <JoyRide
        {...tourState}
        callback={callback}
        showSkipButton={true}
        styles={{
          tooltipContainer: {
            textAlign: "left",
          },

          buttonBack: {
            marginRight: 10,
          },
        }}
        locale={{
          last: "End tour",
        }}
      />
    </>
  );
};
export default Tour;

Conclusion

We’ve seen how to build a product tour in a web UI with React. We’ve also covered some guidelines for making product tours effective.

Now, you can experiment with the React Joyride library and come up with something awesome in your next web app. I would love to hear your views in the comments section below.

Resources

  • Documentation, React Joyride
  • “Seven Exceptional Product Tours, and the Best Practices They Teach Us”, Morgan Brown, Telepathy
  • “The Ultimate Guide to Product Tours and Walkthroughs”, Margaret Kelsey, Appcues