DP
Improving Product-Tour Functionality on a React Site

How to improve product-tour functionality for a site made with React


After figuring out different approaches to improving a product-tour on a web-site, I have decided to create a simplified version with React. Here is the final code with a few adjustments.

MainPage.jsx

import { useMemo, useState } from "react";
import Hint from "./components/Hint";

export default function MainPage() {
  const steps = useMemo(
    () => [
      { id: "title", text: "Title is shown here." },
      { id: "text", text: "Text is shown here." },
    ],
    [],
  );

  const [isTourOpen, setIsTourOpen] = useState(false);
  const [stepIndex, setStepIndex] = useState(0);

  const currentStep = isTourOpen ? steps[stepIndex] : null;

  const startTour = () => {
    setStepIndex(0);
    setIsTourOpen(true);
  };

  const endTour = () => {
    setIsTourOpen(false);
    setStepIndex(0);
  };

  const next = () => {
    setStepIndex((i) => Math.min(i + 1, steps.length - 1));
  };

  const prev = () => {
    setStepIndex((i) => Math.max(i - 1, 0));
  };

  return (
    <main className="flex flex-col items-center justify-center">
      <button
        onClick={()=> (isTourOpen ? endTour() : startTour())}
        className="border-3 z-50 min-w-28 cursor-pointer border-blue-950 bg-blue-800 p-1 text-xs font-bold uppercase text-gray-100 hover:bg-gray-100 hover:text-blue-950"
      >
        {isTourOpen ? "End tour" : "Start tour"}
      </button>

      <section className="relative">
        {currentStep?.id === "title" && (
          <Hint
            text={currentStep.text}
            onClose={endTour}
            onNext={next}
            onPrev={prev}
            isFirst={stepIndex= 0}
            isLast={stepIndex= steps.length - 1}
          />
        )}

        <h2 className="border-3 w-full max-w-2xl bg-blue-950 p-1 text-center text-2xl font-bold">
          Title
        </h2>

        <article className="relative">
          {currentStep?.id === "text" && (
            <Hint
              text={currentStep.text}
              onClose={endTour}
              onNext={next}
              onPrev={prev}
              isFirst={stepIndex= 0}
              isLast={stepIndex= steps.length - 1}
            />
          )}

          <p className="border-3 w-full max-w-2xl bg-blue-950 p-4">
            Lorem ipsum dolor, sit amet consectetur adipisicing elit. Maiores,
            atque, pariatur blanditiis distinctio quam cum minus soluta ea,
            possimus eaque neque perferendis fugiat accusamus assumenda
            voluptatibus molestias corrupti natus hic dolor a in quasi?
            Cupiditate totam iste illo hic ut asperiores laudantium molestias
            sequi nesciunt, a voluptatem, cumque repellat magni harum tempore,
            fuga pariatur. Totam, est nam ullam beatae praesentium sed possimus
            incidunt quo perspiciatis earum. Quisquam doloribus inventore, nobis
            eos tenetur non tempora consequuntur rerum hic at perspiciatis nemo
            reiciendis, alias unde cupiditate modi totam quibusdam recusandae
            est quidem molestiae laudantium a ipsam excepturi. Reprehenderit
            voluptas unde temporibus modi!
          </p>
        </article>
      </section>

      {/* Darkening effect for the page */}
      {isTourOpen && (
        <div
          onClick={endTour}
          className="pointer-events-auto fixed inset-0 z-40 bg-black/40"
          aria-hidden="true"
        />
      )}
    </main>
  );
}

Hint.jsx

export default function Hint({
  text,
  onNext,
  onPrev,
  onClose,
  isFirst,
  isLast,
}) {
  return (
    <aside className="hint-contentWrapper absolute -top-5 z-50 mx-2 w-full max-w-[200px] text-center">
      <div className="border-3 border-blue-950 bg-gray-100 px-2 py-1 text-xs text-blue-950">
        <p className="text-sm">{text}</p>

        <div className="mt-2 flex items-center justify-between gap-2">
          <button
            onClick={onPrev}
            disabled={isFirst}
            className="border-2 border-blue-950 px-2 py-1 hover:bg-blue-950 hover:text-gray-200 disabled:opacity-50"
          >
            Prev.
          </button>

          {isLast ? (
            <button
              onClick={onClose}
              className="border-2 border-blue-950 px-2 py-1"
            >
              Finish
            </button>
          ) : (
            <button
              onClick={onNext}
              className="border-2 border-blue-950 px-2 py-1 hover:bg-blue-950 hover:text-gray-200"
            >
              Next
            </button>
          )}

          <button
            onClick={onClose}
            className="border-2 border-blue-950 px-2 py-1 hover:bg-blue-950 hover:text-gray-200"
          >
            Close
          </button>
        </div>
      </div>

      <div className="border-t-10 border-r-10 border-l-10 mx-auto h-0 w-0 border-l-transparent border-r-transparent border-t-blue-950" />
    </aside>
  );
}

Live version

Styling has been slightly modified to match the design of the current site.

Title

Lorem ipsum dolor, sit amet consectetur adipisicing elit. Maiores, atque, pariatur blanditiis distinctio quam cum minus soluta ea, possimus eaque neque perferendis fugiat accusamus assumenda voluptatibus molestias corrupti natus hic dolor a in quasi? Cupiditate totam iste illo hic ut asperiores laudantium molestias sequi nesciunt, a voluptatem, cumque repellat magni harum tempore, fuga pariatur. Totam, est nam ullam beatae praesentium sed possimus incidunt quo perspiciatis earum. Quisquam doloribus inventore, nobis eos tenetur non tempora consequuntur rerum hic at perspiciatis nemo reiciendis, alias unde cupiditate modi totam quibusdam recusandae est quidem molestiae laudantium a ipsam excepturi. Reprehenderit voluptas unde temporibus modi!


Thank you for reading.