State

useStep

1-indexed step counter for wizards and steppers.

Demo

step
1 / 4

Usage

use-step.tsxtsx
import { useStep } from "hookli";

export function Demo() {
  const [step, { goToNextStep, goToPrevStep, canGoToNextStep, canGoToPrevStep, reset }] =
    useStep(4);

  return (
    <div>
      <p>Step {step} of 4</p>
      <button onClick={goToPrevStep} disabled={!canGoToPrevStep}>Back</button>
      <button onClick={goToNextStep} disabled={!canGoToNextStep}>Next</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}

API

useStep.d.tsts
useStep(maxStep: number): [number, UseStepActions]

Parameters

NameTypeDefaultDescription
maxStepnumberThe highest reachable step (inclusive). Steps run from 1 to maxStep.

Returns

NameTypeDescription
[0] stepnumberThe current step, 1-indexed.
[1] actionsUseStepActionsControls for moving between steps — see below.

UseStepActions

The second tuple element — the stepper controls.

NameTypeDescription
goToNextStep() => voidAdvances to the next step; a no-op at maxStep.
goToPrevStep() => voidGoes back one step; a no-op at step 1.
reset() => voidResets back to step 1.
canGoToNextStepbooleanWhether a next step is available.
canGoToPrevStepbooleanWhether a previous step is available.
setStepDispatch<SetStateAction<number>>Sets the step directly (1-indexed); throws if outside the 1..maxStep range.

Hook

The implementation, straight from hookli. Copy it, or install the package.

src/hooks/use-step/use-step.tstsx
import { Dispatch, SetStateAction, useCallback, useMemo, useState } from "react";

export interface UseStepActions {
  goToNextStep: () => void;
  goToPrevStep: () => void;
  reset: () => void;
  canGoToNextStep: boolean;
  canGoToPrevStep: boolean;
  setStep: Dispatch<SetStateAction<number>>;
}

export const useStep = (maxStep: number): [number, UseStepActions] => {
  const [currentStep, setCurrentStep] = useState(1);

  const canGoToNextStep = useMemo(
    () => currentStep + 1 <= maxStep,
    [currentStep, maxStep],
  );
  const canGoToPrevStep = useMemo(() => currentStep - 1 >= 1, [currentStep]);

  const setStep = useCallback<Dispatch<SetStateAction<number>>>(
    (step) => {
      setCurrentStep((prev) => {
        const newStep = step instanceof Function ? step(prev) : step;
        if (newStep >= 1 && newStep <= maxStep) {
          return newStep;
        }
        throw new Error("Step not valid");
      });
    },
    [maxStep],
  );

  const goToNextStep = useCallback(() => {
    setCurrentStep((prev) => (prev + 1 <= maxStep ? prev + 1 : prev));
  }, [maxStep]);

  const goToPrevStep = useCallback(() => {
    setCurrentStep((prev) => (prev - 1 >= 1 ? prev - 1 : prev));
  }, []);

  const reset = useCallback(() => {
    setCurrentStep(1);
  }, []);

  return [
    currentStep,
    { goToNextStep, goToPrevStep, canGoToNextStep, canGoToPrevStep, setStep, reset },
  ];
};

View source on GitHub

Related hooks