hooks

use-stepper

back

Preview

Step: 0

Name: Dan

Source code

import {
  ReactNode,
  createContext,
  useCallback,
  useContext,
  useState,
} from "react";

export type StepperState<TState> = {
  active: number;
  hasPreviousStep: boolean;
  hasNextStep: boolean;
  state: TState;
  updateState: (state: Partial<TState>) => void;
  set: (step: number) => void;
  next: () => void;
  previous: () => void;
};

export const useStepper = <TState,>(
  max: number,
  defaultState: TState
): StepperState<TState> => {
  const [active, set] = useState(0);
  const [state, setState] = useState(defaultState);

  const next = useCallback(() => {
    set((step) => Math.min(step + 1, max - 1));
  }, [max]);

  const previous = useCallback(() => {
    set((step) => Math.max(step - 1, 0));
  }, []);

  const updateState = useCallback((newState: Partial<TState>) => {
    setState((prev) => ({
      ...prev,
      ...newState,
    }));
  }, []);

  const hasNextStep = Math.min(active + 1, max - 1) !== active;
  const hasPreviousStep = Math.max(active - 1, 0) !== active;

  return {
    active,
    hasPreviousStep,
    hasNextStep,
    state,
    updateState,
    set,
    next,
    previous,
  };
};

const stepperContext = createContext<StepperState<any> | null>(null);

export type StepperProviderProps<TState> = {
  state: StepperState<TState>;
  children: ReactNode;
};

export const StepperProvider = <TState,>({
  state,
  children,
}: StepperProviderProps<TState>) => {
  return (
    <stepperContext.Provider value={state}>{children}</stepperContext.Provider>
  );
};

export const useStepperContext = <TState,>() => {
  const context = useContext(stepperContext);
  if (!context) {
    throw new Error("useStepperContext must be used within a StepperProvider");
  }

  return context as StepperState<TState>;
};