Composables

useWithLoading

Wrap async functions with automatic loading state management while preserving their exact signature.

The useWithLoading composable wraps any function with a reactive isLoading state that automatically tracks the function's execution. It preserves the exact argument and return types of the original function, making it type-safe and flexible.

Basic Usage

const sendEmail = async (email: string) => {
  await $fetch('/api/send-email', { 
    method: 'POST', 
    body: { email } 
  })
}

const { isLoading, fnWithLoading: sendEmailWithLoading } = useWithLoading(sendEmail)

// Call the wrapped function - isLoading will be true during execution
await sendEmailWithLoading('user@example.com')
components/ReviewForm.vue
<script setup lang="ts">
const rating = ref(5)
const comment = ref('')

const submitReview = async (reviewData: { rating: number; comment: string }) => {
  await $fetch('/api/reviews', { 
    method: 'POST',
    body: reviewData
  })
}

const { isLoading, fnWithLoading: postReview } = useWithLoading(submitReview)

async function handleSubmit() {
  await postReview({ rating: rating.value, comment: comment.value })
  comment.value = ''
}
</script>

<template>
  <div>
    <UFormGroup label="Rating">
      <UInput 
        v-model="rating" 
        type="number"
        :disabled="isLoading"
      />
    </UFormGroup>
    
    <UFormGroup label="Comment">
      <UTextarea 
        v-model="comment"
        :disabled="isLoading"
        placeholder="Share your thoughts..."
      />
    </UFormGroup>
    
    <!-- Loading indicator in form area -->
    <div v-if="isLoading" class="my-4 flex items-center gap-2">
      <UIcon name="i-heroicons-arrow-path" class="animate-spin" />
      <span class="text-sm text-gray-600">Submitting your review...</span>
    </div>
    
    <!-- Multiple buttons disabled during submission -->
    <div class="mt-4 flex gap-2">
      <UButton @click="handleSubmit" :disabled="isLoading">
        Submit Review
      </UButton>
      <UButton color="gray" :disabled="isLoading">
        Save Draft
      </UButton>
      <UButton color="gray" variant="ghost" :disabled="isLoading">
        Cancel
      </UButton>
    </div>
  </div>
</template>

Parameters

The composable accepts a single function parameter:

ParameterTypeDescription
fn(...args: Args) => R | Promise<R>The function to wrap. Can be sync or async. All argument types and return types are preserved

Return Value

Returns an object with:

  • isLoading - Reactive Ref<boolean> that's true during function execution
  • fnWithLoading - Wrapped function with identical signature that manages loading state

Examples

Multiple Parameters

const updateUser = async (userId: string, name: string, age: number) => {
  return await $fetch(`/api/users/${userId}`, {
    method: 'PATCH',
    body: { name, age },
  })
}

const { isLoading, fnWithLoading: updateUserWithLoading } = useWithLoading(updateUser)

// Type-safe: all parameters required with correct types
await updateUserWithLoading('123', 'Ada Lovelace', 36)

Return Value Handling

const fetchUserData = async (userId: string) => {
  const data = await $fetch<User>(`/api/users/${userId}`)
  return data
}

const { isLoading, fnWithLoading: getUserData } = useWithLoading(fetchUserData)

// Return type is preserved
const userData = await getUserData('123') // Type: User

Multiple Loading States

const { 
  isLoading: isSaving, 
  fnWithLoading: saveData 
} = useWithLoading(async (data: FormData) => {
  await $fetch('/api/save', { method: 'POST', body: data })
})

const { 
  isLoading: isDeleting, 
  fnWithLoading: deleteData 
} = useWithLoading(async (id: string) => {
  await $fetch(`/api/items/${id}`, { method: 'DELETE' })
})
<template>
  <div>
    <UButton :loading="isSaving" @click="saveData(formData)">
      Save
    </UButton>
    <UButton :loading="isDeleting" @click="deleteData('123')">
      Delete
    </UButton>
  </div>
</template>

Type Safety

The composable uses TypeScript generics to preserve the exact signature:

  • Arguments: All parameter types are maintained
  • Return type: Original return type is preserved (wrapped in Promise if not already)
  • Autocomplete: Full IDE support for both parameters and return values

Copyright © 2026