Skip to content

Added initialStep to useStep #688

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/usehooks-ts/src/useStep/useStep.demo.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { useStep } from './useStep'

export default function Component() {
const [currentStep, helpers] = useStep(5)
const initialStep = 2;
const [currentStep, helpers] = useStep(5, intitialStep)

const {
canGoToPrevStep,
Expand All @@ -14,6 +15,7 @@ export default function Component() {

return (
<>
<p>Initial step is {initialStep}</p>
<p>Current step is {currentStep}</p>
<p>Can go to previous step {canGoToPrevStep ? 'yes' : 'no'}</p>
<p>Can go to next step {canGoToNextStep ? 'yes' : 'no'}</p>
Expand Down
27 changes: 27 additions & 0 deletions packages/usehooks-ts/src/useStep/useStep.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,31 @@ describe('useStep()', () => {

expect(result.current[1].canGoToNextStep).toBe(true)
})

it('should set inital step to 1 if max steps is exceeded', () => {
const { result } = renderHook(() => useStep(2, 3))

expect(result.current[0]).toBe(1)
})

it('should set inital step to 1 if inital step is below minimum', () => {
const { result } = renderHook(() => useStep(2, 0))

expect(result.current[0]).toBe(1)
})

it('should set inital step to 2', () => {
const { result } = renderHook(() => useStep(2, 2))

expect(result.current[0]).toBe(2)
})

it('should throw an error', () => {
const nonInteger = '' as never
vi.spyOn(console, 'error').mockImplementation(() => vi.fn())
expect(() => {
renderHook(() => useStep(2, nonInteger))
}).toThrowError(/initialStep must be an integer/)
vi.resetAllMocks()
})
})
119 changes: 63 additions & 56 deletions packages/usehooks-ts/src/useStep/useStep.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,29 @@
import { useCallback, useState } from 'react'
import { useCallback, useState } from "react";

import type { Dispatch, SetStateAction } from 'react'
import type { Dispatch, SetStateAction } from "react";

/** Represents the second element of the output of the `useStep` hook. */
type UseStepActions = {
/** Go to the next step in the process. */
goToNextStep: () => void
/** Go to the previous step in the process. */
goToPrevStep: () => void
/** Reset the step to the initial step. */
reset: () => void
/** Check if the next step is available. */
canGoToNextStep: boolean
/** Check if the previous step is available. */
canGoToPrevStep: boolean
/** Set the current step to a specific value. */
setStep: Dispatch<SetStateAction<number>>
}
/** Go to the next step in the process. */
goToNextStep: () => void;
/** Go to the previous step in the process. */
goToPrevStep: () => void;
/** Reset the step to the initial step. */
reset: () => void;
/** Check if the next step is available. */
canGoToNextStep: boolean;
/** Check if the previous step is available. */
canGoToPrevStep: boolean;
/** Set the current step to a specific value. */
setStep: Dispatch<SetStateAction<number>>;
};

type SetStepCallbackType = (step: number | ((step: number) => number)) => void
type SetStepCallbackType = (step: number | ((step: number) => number)) => void;

/**
* Custom hook that manages and navigates between steps in a multi-step process.
* @param {number} maxStep - The maximum step in the process.
* @param {number} [initialStep] - The initial step for the current step state (default is `1`).
* @returns {[number, UseStepActions]} An tuple containing the current step and helper functions for navigating steps.
* @public
* @see [Documentation](https://usehooks-ts.com/react-hook/use-step)
Expand All @@ -32,52 +33,58 @@ type SetStepCallbackType = (step: number | ((step: number) => number)) => void
* // Access and use the current step and provided helper functions.
* ```
*/
export function useStep(maxStep: number): [number, UseStepActions] {
const [currentStep, setCurrentStep] = useState(1)
export function useStep(
maxStep: number,
initialStep = 1,
): [number, UseStepActions] {
if (!Number.isInteger(initialStep)) {
throw new Error('initialStep must be an integer');
}
const [currentStep, setCurrentStep] = useState(initialStep > 0 && initialStep <= maxStep ? initialStep : 1);

const canGoToNextStep = currentStep + 1 <= maxStep
const canGoToPrevStep = currentStep - 1 > 0
const canGoToNextStep = currentStep + 1 <= maxStep;
const canGoToPrevStep = currentStep - 1 > 0;

const setStep = useCallback<SetStepCallbackType>(
step => {
// Allow value to be a function so we have the same API as useState
const newStep = step instanceof Function ? step(currentStep) : step
const setStep = useCallback<SetStepCallbackType>(
(step) => {
// Allow value to be a function so we have the same API as useState
const newStep = step instanceof Function ? step(currentStep) : step;

if (newStep >= 1 && newStep <= maxStep) {
setCurrentStep(newStep)
return
}
if (newStep >= 1 && newStep <= maxStep) {
setCurrentStep(newStep);
return;
}

throw new Error('Step not valid')
},
[maxStep, currentStep],
)
throw new Error("Step not valid");
},
[maxStep, currentStep],
);

const goToNextStep = useCallback(() => {
if (canGoToNextStep) {
setCurrentStep(step => step + 1)
}
}, [canGoToNextStep])
const goToNextStep = useCallback(() => {
if (canGoToNextStep) {
setCurrentStep((step) => step + 1);
}
}, [canGoToNextStep]);

const goToPrevStep = useCallback(() => {
if (canGoToPrevStep) {
setCurrentStep(step => step - 1)
}
}, [canGoToPrevStep])
const goToPrevStep = useCallback(() => {
if (canGoToPrevStep) {
setCurrentStep((step) => step - 1);
}
}, [canGoToPrevStep]);

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

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