The useCollectionItem composable fetches individual collection items by ID. It's used internally by CardMini components and can be used anywhere you need to fetch a single item.
function useCollectionItem<T = any>(
collection: string,
id: string | Ref<string> | (() => string)
): Promise<{
item: ComputedRef<T | null>
pending: Ref<boolean>
error: Ref<any>
refresh: () => Promise<void>
}>
'123'userId (a ref)() => props.id<script setup lang="ts">
const { item, pending, error } = await useCollectionItem('users', '123')
</script>
<template>
<div v-if="pending">Loading...</div>
<div v-else-if="error">Error: {{ error }}</div>
<div v-else-if="item">
<h1>{{ item.name }}</h1>
<p>{{ item.email }}</p>
</div>
</template>
<script setup lang="ts">
const props = defineProps<{
userId: string
}>()
// Item refetches automatically when userId changes
const { item, pending } = await useCollectionItem('users', () => props.userId)
</script>
<template>
<div v-if="item">
<h2>{{ item.name }}</h2>
<p>{{ item.bio }}</p>
</div>
</template>
<script setup lang="ts">
const selectedId = ref('123')
const { item, pending } = await useCollectionItem('shopProducts', selectedId)
// Change ID triggers automatic refetch
const changeProduct = (newId: string) => {
selectedId.value = newId // Automatically refetches!
}
</script>
<script setup lang="ts">
interface User {
id: string
name: string
email: string
avatar?: string
}
const { item, pending } = await useCollectionItem<User>('users', '123')
// item is typed as ComputedRef<User | null>
</script>
<script setup lang="ts">
const { item, pending, refresh } = await useCollectionItem('users', '123')
// Force refetch
const reloadUser = async () => {
await refresh()
}
</script>
<template>
<div>
<h1>{{ item?.name }}</h1>
<UButton @click="reloadUser" :loading="pending">
Reload
</UButton>
</div>
</template>
<script setup lang="ts">
const { item, pending, error, refresh } = await useCollectionItem('users', '123')
// Retry on error
const retry = () => {
if (error.value) {
refresh()
}
}
</script>
<template>
<div v-if="pending">
<USkeleton class="h-20 w-full" />
</div>
<div v-else-if="error" class="border border-red-200 rounded p-4">
<p class="text-red-600">Failed to load user</p>
<UButton @click="retry" color="red" variant="ghost" size="sm">
Try Again
</UButton>
</div>
<div v-else-if="item">
<h1>{{ item.name }}</h1>
</div>
<div v-else class="text-gray-500">
User not found
</div>
</template>
This is the primary use case - fetching items for reference field display:
<script setup lang="ts">
const props = defineProps<{
id: string
collection: string
}>()
// CardMini automatically uses this composable
const { item, pending, error } = await useCollectionItem(
props.collection,
() => props.id
)
</script>
<template>
<div class="group relative">
<USkeleton v-if="pending" class="h-12 w-full" />
<div v-else-if="item" class="border rounded-md p-2">
<!-- Custom layout here -->
<div>{{ item.title }}</div>
</div>
<div v-else-if="error" class="text-red-500 text-xs">
Error loading
</div>
</div>
</template>
Each item gets its own cache entry based on collection and ID:
// Cache keys are generated as:
collection-item-users-123
collection-item-shopProducts-456
collection-item-locations-789
// Items are cached using Nuxt's useAsyncData cache
// Cache is shared across components
// Updates automatically invalidate related caches
Benefits:
When you update an item, its cache is automatically invalidated:
<script setup lang="ts">
const { item } = await useCollectionItem('users', '123')
const { update } = useCollectionMutation('users')
const updateUser = async () => {
await update('123', { name: 'New Name' })
// Item cache for users:123 is invalidated
// useCollectionItem automatically refetches
// item.value now has updated data!
}
</script>
The composable determines the correct API path based on your current route:
// Current route: /teams/acme-corp/bookings
// Fetches from: /api/teams/acme-corp/users/123
const { item } = await useCollectionItem('users', '123')
// Current route: /super-admin/settings
// Fetches from: /api/super-admin/users/123
const { item } = await useCollectionItem('users', '123')
If your collection uses a custom API path, configure it in the collection:
// collections/locations/nuxt.config.ts
export default defineNuxtConfig({
crouton: {
apiPath: 'custom-locations'
}
})
// Fetches from: /api/teams/acme-corp/custom-locations/123
const { item } = await useCollectionItem('locations', '123')
| Feature | useCollectionItem | useCollectionQuery |
|---|---|---|
| Purpose | Fetch single item | Fetch list of items |
| Input | Item ID | Optional query params |
| Returns | Single object | Array of objects |
| Cache Key | collection-item-{name}-{id} | collection:{name}:{query} |
| Use Case | CardMini, detail views | Tables, lists, forms |
When to use useCollectionItem:
When to use useCollectionQuery:
Always show a skeleton while loading:
<USkeleton v-if="pending" class="h-12 w-full rounded" />
<div v-else-if="item">
{{ item.name }}
</div>
Check if item exists before rendering:
<template>
<div v-if="item">
<!-- Safe to access item.* here -->
<h1>{{ item.title }}</h1>
</div>
</template>
Use optional chaining for deeply nested properties:
<script setup lang="ts">
const { item } = await useCollectionItem('users', '123')
</script>
<template>
<!-- Safe even if item is null or address is undefined -->
<div>{{ item?.address?.city }}</div>
</template>
React to item changes:
<script setup lang="ts">
const { item } = await useCollectionItem('users', () => props.userId)
// Run side effects when item loads or changes
watch(item, (newItem) => {
if (newItem) {
console.log('Item loaded:', newItem)
// Update page title, analytics, etc.
}
}, { immediate: true })
</script>
If the item doesn't exist, item will be null (not an error):
<div v-if="item === null && !pending">
Item not found
</div>
Network failures set error:
<div v-if="error">
Failed to load: {{ error.message }}
<UButton @click="refresh">Retry</UButton>
</div>
API returns 403, handled as error:
<div v-if="error?.statusCode === 403">
You don't have permission to view this item
</div>
interface Product {
id: string
name: string
price: number
stock: number
}
const { item } = await useCollectionItem<Product>('shopProducts', '123')
// item is ComputedRef<Product | null>
// TypeScript knows item.name, item.price, etc.
const { item, pending } = await useCollectionItem<Product>('shopProducts', '123')
// Type guard for template
const hasStock = computed(() => {
return item.value !== null && item.value.stock > 0
})
✅ DO:
❌ DON'T:
useCollectionQuery instead)