Api Reference

useCollectionItem

Fetch a single collection item by ID with automatic caching and reactivity

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

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

FeatureuseCollectionItemuseCollectionQuery
PurposeFetch single itemFetch list of items
InputItem IDOptional query params
ReturnsSingle objectArray of objects
Cache Keycollection-item:{name}:{id}collection:{name}:{query}
Use CaseCardMini, detail viewsTables, 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 useCollectionQuery instead)