How to Add Simple Onboarding Screens for Better UX in ReactJS

How to Add Simple Onboarding Screens for Better UX in ReactJS

The importance, examples, and how-to guide

The React Newbie's photo
The React Newbie
ยทJul 6, 2022ยท

11 min read

Subscribe to my newsletter and never miss my upcoming articles

Play this article

Table of contents

Hello Friend ๐Ÿ˜Ž

I recently learned about the usefulness of onboarding flows/screens in web applications (and mobile apps too).

Importance of Onboarding flows/screens

Here is a quote from an article I found online about onboarding screens:

Onboarding ... tour/walkthrough can help you quickly show the value and know-how of your website to a visitor, and can result in:

  1. A low bounce rate
  2. Higher session durations
  3. Better product adoption
  4. Increased retention - userguiding.com

Onboarding screens are not something new because many applications we use today make use of it.

Fiverr business uses an onboarding modal to introduce new users to its SaaS as shown in the image below:

onboarding.png

Hashnode also uses a set of onboarding screens to help users get started and create their accounts quickly. One such screen is shown below:

step1.png

As you can see, onboarding screens can be a full page, a modal, or even a sidebar. The point is that whenever they are used, they serve specific purposes.

In this article, we will see how we can create a simple onboarding modal that will serve as a product tour for a demo app.

We will use ReactJS, TailwindCSS, and HeadlessUI.

Are you ready? Let's begin...

What you'll learn

  1. How to use React Custom Hooks to handle the logic of your app.
  2. How to add a lightweight transition to your React app with HeadlessUI.

Why are we even using React Custom Hooks in this case.

Simple answer: Reusability

The logic we will implement is a navigation logic and so very likely we will need to add navigation to other parts of our app UI.

Custom Hooks are perfect for reusability because they only exist locally inside a component and several other components can use them separately from each other.

Custom Hooks also eliminates the need to pass states down from parent to child as props which will cause unnecessary rerenders.

I'll leave a link to an article on Custom Hooks at the end of this article.

What we will build

A modal that has three slides. We will toggle between the slides using a Next button. See the demo below:

onboarding.gif

Setup

I created a quick guide to set up a ReactJS project with TailwindCSS added to it.

View it here

Done? Great! Let's continue.

Create the Custom Hook

Inside the src folder, create a new folder with the name hooks. We will save any custom hook we create here.

Now create a new file inside the hooks folder with the name useNavigation.js.

Paste this code into it:

import React, { useState } from "react"

export default function useNavigation () {
    const [navIndex, setNavIndex] = useState(0)

    const navHandler = (direction) => {
        if(direction === "next"){
            setNavIndex(currentIndex => currentIndex + 1)
        }
        if(direction === "previous"){
            setNavIndex(currentIndex => currentIndex - 1)
        }
    }
    return { navIndex, navHandler}
}

Explanation:

  1. We first import useState from "react". useState is a React hook that lets us save data in state in a functional component.
  2. Now we create a state variable navIndex together with setNavIndex, a function that lets us update/mutate the value of navIndex.
  3. We set the initial value of navIndex to 0 with this:
    const [navIndex, setNavIndex] = useState(0)
    
  4. Next we created navHandler, a handler (or a function) that handles the navigation logic using the value of navIndex.
  5. navHandler takes in an argument which we call direction. direction can either be "next" or "previous" and this will determine if the navigation will go forward or backward.
  6. The last thing to note is that we are increasing the value of navIndex if we pass in next to navHandler and we are doing the opposite if we pass previous.

Create the layout for the app

Go to App.js, clear all the codes in it, and paste this:

import React from "react";
import Modal from "./components/Modal";

export default function App() {
  return (
    <div className="">
      {/* add main app content will be here */}
      <div className="">
        <nav className="flex justify-between bg-slate-700 text-white py-4 px-10">
          <span>Logo</span>
          <span>โ˜€</span>
        </nav>
      </div>

      {/* Our onboarding screens will be in a modal */}
      <Modal />
    </div>
  );
}

Explanation:

  1. Our app will have main contents but the contents will be behind a modal.
  2. The modal will hold our onboarding slides (in this case).

Let's create a Modal.js file inside a new folder. Name the folder components and save Modal.js inside it.

Now add this code to Modal.js:

import React from "react";

export default function Modal() {
  return (
    <div className="static">
      <div className="fixed h-screen w-screen bg-black z-10 top-0 opacity-50"></div>
      <div className="fixed top-0 right-0 left-0 z-20 flex justify-center">
        <div className="mt-24 mx-4 my-4 bg-white w-[420px] h-[420px] overflow-hidden relative">
          <svg
            xmlns="http://www.w3.org/2000/svg"
            className="h-6 w-6 absolute z-40 right-3 top-3 cursor-pointer"
            fill="none"
            viewBox="0 0 24 24"
            stroke="currentColor"
            strokeWidth={2}
          >
            <path
              strokeLinecap="round"
              strokeLinejoin="round"
              d="M6 18L18 6M6 6l12 12"
            />
          </svg>

          {/* modal content will be here  */}
          <div className="">
            <div className="bg-slate-300 flex flex-col items-center pt-20 h-screen">
              {/* content image will be here */}
              <svg
                xmlns="http://www.w3.org/2000/svg"
                className="h-60 w-60"
                fill="none"
                viewBox="0 0 24 24"
                stroke="currentColor"
                strokeWidth={2}
              >
                <path
                  strokeLinecap="round"
                  strokeLinejoin="round"
                  d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
                />
              </svg>
              {/* content text here */}
              <h4 className="flex justify-center text-red-600 font-semibold italic text-xl pt-3">
                You're all set!
              </h4>
            </div>
          </div>
          {/* modal content ends here  */}


          {/* navigation  on line 49 */}

          <div className="flex flex-col items-center py-4 absolute bottom-0 right-0 left-0 bg-white">
            <div className="mb-2">1/3</div>
            <button className="py-2 px-20 rounded-xl bg-slate-600 text-white">
              Next
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

When we go back to the app, we should have this:

onb1.png

Cool right?

The layout for onboarding is ready.

Notice the next button at the bottom? We will use it to implement our useNavigation custom hook.

Let's do that.

Implement the Custom Hook

To implement the custom hook, change line 49 in Modal.js to:

{      
        navIndex <= 2 && (
            <div className="flex flex-col items-center py-4 absolute bottom-0 right-0 left-0 bg-white">
              <div className="mb-2">{navIndex + 1}/3</div>
              <button
                onClick={() => navHandler("next")}
                className="py-2 px-20 rounded-xl bg-slate-600 text-white"
              >
                {navIndex === 2 ? "Done" : "Next"}
              </button>
            </div>
          )
}

Explanation:

  1. We want to conditionally show the navigation and the condition is that the navigation should only show on the first 3 slides.
  2. We implement useNavigation by adding navHandler to the onClick event like this: onClick={() => navHandler("next")}. This means that when the user clicks on the Next button, navHandler will be called. Notice that navHandler has a string parameter "next".
  3. Lastly, we use navIndex to decide what the text of our button will be. Either Done or Next

Don't forget to import useNavigation.js in your Modal.js file like this:

import React from "react";
import useNavigation from "../hooks/useNavigation"; // add this

export default function Modal() {
  const { navIndex, navHandler } = useNavigation(); // and add this
...

Now in other to test if our useNavigation logic is working, let's fill up the modal with content.

Adding real content to modal to test the custom hook

Inside the components folder, create 4 new files: CompOne.js:

import React from "react";
import img from "../images/1.png";

export default function CompOne() {
  return (
    <div className="">
      <div className="bg-red-300 flex justify-center">
        <img src={img} alt="bg" className="w-1/2" />
      </div>
      <h4 className="flex justify-center text-red-600 font-semibold italic text-xl pt-3">
        Welcome to our app!
      </h4>
      <span className="flex justify-center">
        Get on the right track towards mindfullness
      </span>
    </div>
  );
}

CompTwo.js:

import React from "react";
import img from "../images/2.png";

export default function CompTwo() {
  return (
    <div className="">
      <div className="bg-slate-300 flex justify-center">
        <img src={img} alt="bg" className="w-1/2" />
      </div>
      <h4 className="flex justify-center text-red-600 font-semibold italic text-xl pt-3">
        Get started with Yoga
      </h4>
      <span className="flex justify-center">
        Easily get started in your yoga journey
      </span>
    </div>
  );
}

CompThree.js:

import React from "react";
import img from "../images/3.png";

export default function CompThree() {
  return (
    <div className="">
      <div className="bg-indigo-300 flex justify-center">
        <img src={img} alt="bg" className="w-1/2" />
      </div>
      <h4 className="flex justify-center text-red-600 font-semibold italic text-xl pt-3">
        Get your back
      </h4>
      <span className="flex justify-center">
        Set up different measures to start with your life
      </span>
    </div>
  );
}

Ready.js:

import React from "react";

export default function Ready() {
  return (
    <div className="">
      <div className="bg-slate-300 flex flex-col items-center pt-20 h-screen">
        <svg
          xmlns="http://www.w3.org/2000/svg"
          className="h-60 w-60"
          fill="none"
          viewBox="0 0 24 24"
          stroke="currentColor"
          strokeWidth={2}
        >
          <path
            strokeLinecap="round"
            strokeLinejoin="round"
            d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
          />
        </svg>
        <h4 className="flex justify-center text-red-600 font-semibold italic text-xl pt-3">
          You're all set!
        </h4>
      </div>
    </div>
  );
}

Notice that for CompOne, CompTwo, and CompThree, I imported an image from a folder named images. If you don't have this folder already, create one now and save 3 different images in it for use. Remember to use the correct name of the image when importing. In my case, I used /3.png.

See our folder structure so far and check if it corresponds with yours:

file.png

These 4 components we just created are simply the contents for the modal so not much explanation is required for it.

Let's import them intoModal.js like this:

import React from "react";
import CompOne from "./CompOne"; // just added
import CompTwo from "./CompTwo"; // just added
import CompThree from "./CompThree"; // just added
import Ready from "./Ready"; // just added
import useNavigation from "../hooks/useNavigation";

export default function Modal() {
  const { navIndex, navHandler } = useNavigation();

// keep the rest of the code for Modal.js here

Remove all the code from {/* modal content will be here */} to {/* modal content ends here */} inside Modal.js and replace it with this:

{/* modal content will be here  */}
  {navIndex === 0 && <CompOne />}
  {navIndex === 1 && <CompTwo />}
  {navIndex === 2 && <CompThree />}
  {navIndex === 3 && <Ready />}
{/* modal content ends here  */}

Explanation:

  1. We are rendering each of the 4 components we just created conditionally based on the value of navIndex.
  2. So if navIndex is 0 render CompOne, if navIndex is 1 render CompTwo, and so on.
  3. This conditional rendering we are implementing will work because the value of navIndex changes whenever we click the next button (remember? ๐Ÿ˜Ž).

Go back to the browser and test it! ๐Ÿ‘‡

test onboarding.gif

Is yours working? Great!

Pat yourself on the back. (I just did the same to myself ๐Ÿ˜)

One last thing.

Notice that the components are just appearing and disappearing when you click next? Doesn't look cool right?

Let's add a nice animation as a final touch

Pretty easy stuff.

First, head back to the terminal, stop your server and run this code below:

npm install @headlessui/react

Nice, we've installed just what we needed.

To use HeadlessUI (what we just installed), go to Modal.js and import it like this:

import React from "react";
import CompOne from "./CompOne";
import CompTwo from "./CompTwo";
import CompThree from "./CompThree";
import Ready from "./Ready";
import useNavigation from "../hooks/useNavigation";

import { Transition } from "@headlessui/react"; //just added

export default function Modal() {
  const { navIndex, navHandler } = useNavigation();

// keep the rest of the code for Modal.js here

Now replace the code that exists between {/* modal content will be here */} and {/* modal content ends here */} with this code:

{/* modal content will be here  */}
          <Transition
            show={navIndex === 0}
            enter="transition ease-in-out duration-700 transform"
            enterFrom="-translate-x-full"
            enterTo="translate-x-0"
            leave="transition ease-in-out duration-500 transform"
            leaveFrom="-translate-x-0"
            leaveTo="translate-x-full"
          >
            <CompOne />
          </Transition>
          <Transition
            show={navIndex === 1}
            enter="transition ease-in-out duration-700 transform"
            enterFrom="-translate-x-full"
            enterTo="translate-x-0"
            leave="transition ease-in-out duration-500 transform"
            leaveFrom="-translate-x-0"
            leaveTo="translate-x-full"
          >
            <CompTwo />
          </Transition>
          <Transition
            show={navIndex === 2}
            enter="transition ease-in-out duration-700 transform"
            enterFrom="-translate-x-full"
            enterTo="translate-x-0"
            leave="transition ease-in-out duration-500 transform"
            leaveFrom="-translate-x-0"
            leaveTo="translate-x-full"
          >
            <CompThree />
          </Transition>

          <Transition
            show={navIndex === 3}
            enter="transition ease-in-out duration-700 transform"
            enterFrom="-translate-x-full"
            enterTo="translate-x-0"
            leave="transition ease-in-out duration-500 transform"
            leaveFrom="-translate-x-0"
            leaveTo="translate-x-full"
          >
            <Ready />
          </Transition>
{/* modal content ends here  */}

Now restart your server by running this on the terminal:

npm start

Head back to your browser and check the animation ๐Ÿ‘‡

onboarding.gif

You can agree with me now that it's a bit better now right?. ๐Ÿ‘

How HeadlessUI works

  1. It opens with the tag <Transition> and closes with </Transition>.
  2. The element we want to animate will be in between the opening and closing tag.
  3. Within the opening tag <Transition>, we specify the following things:
  4. the condition that decides if an element will be shown or hidden inside the show attribute.
    show={navIndex === 3}
    
  5. how the element animation will be when it is being rendered (or shown or when it is entering the screen)
    enter="transition ease-in-out duration-700 transform"
    enterFrom="-translate-x-full"
    enterTo="translate-x-0"
    
  6. how the element animation will be when it is being removed (or hidden or when it is leaving the screen)
    enter="transition ease-in-out duration-700 transform"
    enterFrom="-translate-x-full"
    enterTo="translate-x-0"
    
    And that's just it. As easy as that.

Now please note that transition ease-in-out duration-700 transform, -translate-x-full and translate-x-0are TailwindCSS utility classes.

Yes, HeadlessUI was built to use TailwindCSS classes.

I will drop the links to TailwindCSS transition and Animation classes as well as HeadlessUI documentation at the bottom of the post.

For now, that's it. We are done!

Whew! It wasn't easy writing this but I'm glad I did.

If you found it useful, please subscribe to my newsletter, follow me here on hashnode or on Twitter (I post useful stuff there too)

Just before you go

I just realized that we didn't implement the logic for closing our onboarding modal. You can do it yourself as an assignment.

Use this article I wrote here about creating a modal as reference.

Thank you and see ya later โœŒ๏ธ

Useful resources

  1. TailwindCSS transition and Animation classes
  2. HeadlessUI transition documentation
  3. Top 5 React Onboarding Libraries for Product Tours and Walkthroughs.
  4. React custom Hooks are easy than you think !!
ย 
Share this