Skip to content

Instantly share code, notes, and snippets.

@nash403
Last active March 28, 2025 11:45
Show Gist options
  • Save nash403/4e1f32f0cad0c75b50c2a9ab42045770 to your computer and use it in GitHub Desktop.
Save nash403/4e1f32f0cad0c75b50c2a9ab42045770 to your computer and use it in GitHub Desktop.
My helper for building a multi-step linear wizard interface. Base on https://vueuse.org/core/useStepper/#usestepper
import { useStepper } from '@vueuse/core'
import { ref, toValue } from 'vue'
/**
* A wrapper around VueUse's `useStepper` that adds async-aware navigation methods.
* It tracks direction of navigation (forward/backward) and only transitions between steps
* if the provided async action succeeds.
*
* The transition functions (`goXXX`) return a tuple `[success, error, result]`:
* - `success`: Boolean indicating whether the async action succeeded.
* - `error`: The error caught during the action (if any).
* - `result`: The result returned from the async action.
*/
export function useStepperWithAsync(steps, initialStep) {
const stepper = useStepper(steps, initialStep)
const isGoingBack = ref(false)
const isGoingNext = ref(false)
const transitionState = ref({ isGoingBack, isGoingNext }) // store to add other custom values on runtime if you want for example a `isExiting` state
async function goToAsync(step, actionFn, ...args) {
const stepExists = stepper.stepNames.value.includes(step)
if (stepExists) {
if (stepper.isBefore(step)) {
isGoingBack.value = true
} else if (stepper.isAfter(step)) {
isGoingNext.value = true
}
}
const [success, err, data] = await _execAsyncAction(actionFn, ...args)
if (success) {
stepper.goTo(step)
}
return [success, err, data]
}
async function goToNextAsync(actionFn, ...args) {
isGoingNext.value = true
const [success, err, data] = await _execAsyncAction(actionFn, ...args)
if (success) {
stepper.goToNext()
}
return [success, err, data]
}
async function goToPreviousAsync(actionFn, ...args) {
isGoingBack.value = true
const [success, err, data] = await _execAsyncAction(actionFn, ...args)
if (success && !stepper.isFirst.value) {
stepper.goToPrevious()
}
return [success, err, data]
}
async function goBackToAsync(step, actionFn, ...args) {
isGoingBack.value = true
const [success, err, data] = await _execAsyncAction(actionFn, ...args)
if (success) {
stepper.goBackTo(step)
}
return [success, err, data]
}
function reset() {
resetTransitionState()
stepper.index.value = stepper.stepNames.value.indexOf(toValue(initialStep) ?? stepper.stepNames.value[0])
}
function resetTransitionState() {
// reset all runtime added keys
for (const key in transitionState.value) {
transitionState.value[key] = false
}
}
async function _execAsyncAction(actionFn, ...args) {
try {
const result = await actionFn?.(...args)
if (result || result === undefined) {
return [true, null, result]
}
} catch (e) {
[false, e, null]
} finally {
resetTransitionState()
}
}
return {
...stepper,
isGoingBack,
isGoingNext,
transitionState,
goToAsync,
goToNextAsync,
goToPreviousAsync,
goBackToAsync,
resetTransitionState,
reset,
_execAsyncAction,
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment