NextStep - Lightweight Next.js Onboarding Library Logo

Validation

Add validation logic to your onboarding tours to ensure users complete required actions before proceeding to the next step.

Overview

Validation allows you to create more interactive and guided onboarding experiences by ensuring users complete specific actions before moving to the next step. This is particularly useful for:

  • Form validations during onboarding
  • Ensuring users click specific buttons or interact with elements
  • Verifying user input before proceeding
  • Ensuring users have permissions to access certain features
  • And many more! Creating more engaging step-by-step tutorials

How Validation Works

Validation in NextStep works through custom card components that can validate user actions before allowing progression to the next step. When validation fails, users receive feedback and cannot proceed until they complete the required action.

We chose this approach because it allows you to create your own validation logic, options are limitless.

Creating a Validation Card Component

To implement validation, you'll need to create a custom card component that includes validation logic. Here's an example:

'use client';

import { CardComponentProps, useNextStep } from 'nextstepjs';
import {
  Card,
  CardContent,
  CardFooter,
  CardHeader,
  CardTitle,
} from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import validation from '@/lib/validation';
import { toast } from '@/hooks/use-toast';

const CardWithValidation: React.FC<CardComponentProps> = ({
  step,
  currentStep,
  totalSteps,
  nextStep,
  prevStep,
  skipTour,
  arrow,
}) => {
  const { currentTour } = useNextStep();

  // Get the validation function for the current step
  const validate =
    currentTour && validation[currentTour] && validation[currentTour][currentStep]
      ? validation[currentTour][currentStep].validation
      : () => true;

  // Get the validation message for the current step
  const validationMessage =
    currentTour && validation[currentTour] && validation[currentTour][currentStep]
      ? validation[currentTour][currentStep].validationMessage
      : '';

  return (
    <Card className="w-[350px]">
      <CardHeader>
        <CardTitle className="flex items-center justify-between">
          <p>{step.title}</p>
          {step.icon}
        </CardTitle>
      </CardHeader>
      <CardContent>
        <p>{step.content}</p>
        {arrow}
      </CardContent>
      <CardFooter className="flex flex-col">
        {step.showControls && (
          <div className="flex justify-between w-full">
            <Button onClick={prevStep} disabled={currentStep === 0} variant="outline">
              Previous
            </Button>
            <Button
              // Validate the current step and if it passes, go to the next step
              // If it fails, show the validation message
              onClick={async () => {
                if (await validate()) {
                  nextStep();
                } else {
                  toast({
                    title: validationMessage,
                    variant: 'destructive',
                  });
                }
              }}
            >
              {currentStep === totalSteps - 1 ? 'Finish' : 'Next'}
            </Button>
          </div>
        )}
        {step.showSkip && (
          <Button onClick={skipTour} variant={'ghost'} className="w-full">
            Skip Tour
          </Button>
        )}
      </CardFooter>
    </Card>
  );
};

export default CardWithValidation;

Example Validations

This is how you can create your own validation logic.

interface ValidationStep {
  validation: () => boolean | Promise<boolean>;
  validationMessage: string;
}

interface ValidationTour {
  [stepIndex: number]: ValidationStep;
}

interface ValidationConfig {
  [tourName: string]: ValidationTour;
}

const validation: ValidationConfig = {
  'validation-demo': {
    0: {
      validation: async () => {
        console.log('Validating step 0');
        await new Promise((resolve) => setTimeout(resolve, 1000));
        return true;
      },
      validationMessage: 'Step 0 validation failed',
    },
    1: {
      validation: () => {
        console.log('Validating step 1');
        // Check window width return true if width is greater than 768
        return window.innerWidth > 768;
      },
      validationMessage: 'Window width must be greater than 768',
    },
    2: {
      validation: () => {
        console.log('Validating step 2');
        // Check if window width is less than 768
        return window.innerWidth < 768;
      },
      validationMessage: 'Window width must be less than 768',
    },
  },
};

export default validation;

Using Validation Card Component

To use your validation card component, pass it to the cardComponent prop of the NextStep component:

import { NextStepReact, NextStepProvider } from 'nextstepjs';
import CardWithValidation from './CardWithValidation';
import steps from './steps';

function MyApp() {
  return (
    <NextStepProvider>
      <NextStepReact 
        steps={steps} 
        cardComponent={CardWithValidation}
      >
        {/* Your app content */}
      </NextStepReact>
    </NextStepProvider>
  );
}

Validation Examples

Form Field Validation

const formValidation = {
  validation: () => {
    const input = document.getElementById('username') as HTMLInputElement;
    return input && input.value.length >= 3;
  },
  validationMessage: 'Please enter a username with at least 3 characters.',
};

Button Click Validation

const buttonClickValidation = {
  validation: () => {
    return localStorage.getItem('button-clicked') === 'true';
  },
  validationMessage: 'Please click the "Get Started" button to continue.',
};

// In your component, set the flag when button is clicked
const handleButtonClick = () => {
  localStorage.setItem('button-clicked', 'true');
  // Your button logic here
};

Async Validation

const asyncValidation = {
  validation: async () => {
    try {
      const response = await fetch('/api/validate-user');
      const data = await response.json();
      return data.isValid;
    } catch (error) {
      return false;
    }
  },
  validationMessage: 'Please complete the required verification.',
};