useCollectionItem
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.
Type Signature
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>
}>
Parameters
- collection (string) - The collection name (e.g., 'users', 'shopProducts')
- id (string | Ref | Function) - The item ID. Can be:
- Static string:
'123' - Reactive ref:
userId(a ref) - Getter function:
() => props.id
- Static string:
Returns
- item - Computed reference to the fetched item (null if not found)
- pending - Boolean ref indicating loading state
- error - Error object if fetch failed
- refresh - Function to manually refetch the item
Basic Usage
Static 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>
Reactive ID (from props)
<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>
Reactive ID (from ref)
<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>
Advanced Usage
With TypeScript Types
<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>
Manual Refresh
<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>
Error Handling
<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>
In Custom CardMini Components
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>
Caching Behavior
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 useFetch cache
// Cache is shared across components
// Updates automatically invalidate related caches
Benefits:
- Multiple components showing the same item share one request
- Navigating back to a page shows cached data instantly
- Mutations automatically invalidate item caches
Cache Invalidation
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>
API Routes
The composable determines the correct API path based on your current route:
Team-Scoped Routes
// Current route: /teams/acme-corp/bookings
// Fetches from: /api/teams/acme-corp/users/123
const { item } = await useCollectionItem('users', '123')
Super Admin Routes
// Current route: /super-admin/settings
// Fetches from: /api/super-admin/users/123
const { item } = await useCollectionItem('users', '123')
Custom API Paths
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')
Comparison with useCollectionQuery
| 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:
- Fetching a single item by ID
- CardMini components
- Detail pages
- Referenced entity display
When to use useCollectionQuery:
- Fetching lists of items
- Tables and lists
- Search and filter
- Pagination
Common Patterns
Loading Skeleton
Always show a skeleton while loading:
<USkeleton v-if="pending" class="h-12 w-full rounded" />
<div v-else-if="item">
{{ item.name }}
</div>
Conditional Rendering
Check if item exists before rendering:
<template>
<div v-if="item">
<!-- Safe to access item.* here -->
<h1>{{ item.title }}</h1>
</div>
</template>
Optional Chaining
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>
Watchers on Items
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>
Error Scenarios
Item Not Found
If the item doesn't exist, item will be null (not an error):
<div v-if="item === null && !pending">
Item not found
</div>
Network Error
Network failures set error:
<div v-if="error">
Failed to load: {{ error.message }}
<UButton @click="refresh">Retry</UButton>
</div>
Permission Denied
API returns 403, handled as error:
<div v-if="error?.statusCode === 403">
You don't have permission to view this item
</div>
TypeScript Support
Typed Item
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.
Type Guards
const { item, pending } = await useCollectionItem<Product>('shopProducts', '123')
// Type guard for template
const hasStock = computed(() => {
return item.value !== null && item.value.stock > 0
})
Best Practices
✅ DO:
- Use reactive ID parameters when ID can change
- Handle all three states: pending, error, and loaded
- Use TypeScript types for better DX
- Show loading skeletons for better UX
- Use optional chaining for nested properties
❌ DON'T:
- Forget to handle error states
- Access item properties without checking if item exists
- Fetch item data manually (let the composable handle it)
- Use this for lists (use
useCollectionQueryinstead)
Related APIs
- useCollectionQuery - Fetch lists of items
- useCollectionMutation - Create, update, delete items
- CardMini Components - Display components using this composable
- Custom CardMini Guide - Step-by-step guide to creating custom cards