Claude
Skills
Sign in
Back

nuxt-data

Included with Lifetime
$97 forever

| Nuxt 4 data management: composables, data fetching with useFetch/useAsyncData, and state management with useState and Pinia. Use when: creating custom composables, fetching data with useFetch or useAsyncData, managing global state with useState, integrating Pinia, debugging reactive data issues, or implementing SSR-safe state patterns.

General

What this skill does

# Nuxt 4 Data Management

Composables, data fetching, and state management patterns for Nuxt 4 applications.

## Quick Reference

### Data Fetching Methods

| Method | Use Case | SSR | Caching | Reactive |
|--------|----------|-----|---------|----------|
| `useFetch` | Simple API calls | Yes | Yes | Yes |
| `useAsyncData` | Custom async logic | Yes | Yes | Yes |
| `$fetch` | Client-side only, events | No | No | No |

### Composable Naming

| Prefix | Purpose | Example |
|--------|---------|---------|
| `use` | State/logic composable | `useAuth`, `useCart` |
| `fetch` | Data fetching only | `fetchUsers` (rare) |

## When to Load References

**Load `references/composables.md` when:**
- Writing custom composables with complex state
- Debugging state management issues or memory leaks
- Implementing SSR-safe patterns with browser APIs
- Building authentication or complex state composables
- Understanding singleton vs per-call composable patterns

**Load `references/data-fetching.md` when:**
- Implementing API data fetching with reactive parameters
- Troubleshooting shallow vs deep reactivity issues
- Debugging data not refreshing when params change
- Implementing pagination, infinite scroll, or search
- Understanding transform functions, caching, or error handling

**Load `references/pinia-integration.md` when:**
- Setting up Pinia for complex state management
- Creating stores with getters and actions
- Integrating Pinia with SSR
- Persisting state across page reloads

## Composables

### useState - The Foundation

`useState` creates SSR-safe, shared reactive state that persists across component instances.

```typescript
// composables/useCounter.ts
export const useCounter = () => {
  // Singleton - shared across all components
  const count = useState('counter', () => 0)

  const increment = () => count.value++
  const decrement = () => count.value--
  const reset = () => count.value = 0

  return { count, increment, decrement, reset }
}
```

### useState vs ref - Critical Distinction

```typescript
// CORRECT: Shared state (singleton pattern)
export const useAuth = () => {
  const user = useState('auth-user', () => null)  // Shared!
  return { user }
}

// WRONG: Creates new instance every call!
export const useAuth = () => {
  const user = ref(null)  // Not shared!
  return { user }
}
```

**Rule**: Use `useState` for shared/global state. Use `ref` for local component state only.

### Complete Authentication Composable

```typescript
// composables/useAuth.ts
export const useAuth = () => {
  const user = useState<User | null>('auth-user', () => null)
  const isAuthenticated = computed(() => !!user.value)
  const isLoading = useState('auth-loading', () => false)

  const login = async (email: string, password: string) => {
    isLoading.value = true
    try {
      const data = await $fetch('/api/auth/login', {
        method: 'POST',
        body: { email, password }
      })
      user.value = data.user
      return { success: true }
    } catch (error) {
      return { success: false, error: error.message }
    } finally {
      isLoading.value = false
    }
  }

  const logout = async () => {
    await $fetch('/api/auth/logout', { method: 'POST' })
    user.value = null
    navigateTo('/login')
  }

  const checkSession = async () => {
    if (import.meta.server) return  // Skip on server
    try {
      const data = await $fetch('/api/auth/session')
      user.value = data.user
    } catch {
      user.value = null
    }
  }

  return { user, isAuthenticated, isLoading, login, logout, checkSession }
}
```

### SSR-Safe Browser APIs

```typescript
// composables/useLocalStorage.ts
export const useLocalStorage = <T>(key: string, defaultValue: T) => {
  const data = useState<T>(key, () => defaultValue)

  // Only access localStorage on client
  if (import.meta.client) {
    const stored = localStorage.getItem(key)
    if (stored) {
      data.value = JSON.parse(stored)
    }

    // Watch and persist changes
    watch(data, (newValue) => {
      localStorage.setItem(key, JSON.stringify(newValue))
    }, { deep: true })
  }

  return data
}
```

## Data Fetching

### useFetch - Basic Usage

```typescript
// Simple GET request
const { data, error, pending, refresh } = await useFetch('/api/users')

// With options
const { data: users } = await useFetch('/api/users', {
  method: 'GET',
  query: { limit: 10, offset: 0 },
  headers: { 'X-Custom-Header': 'value' }
})
```

### Reactive Parameters

```vue
<script setup lang="ts">
const page = ref(1)
const search = ref('')

// Auto-refetches when page or search changes
const { data: users, pending } = await useFetch('/api/users', {
  query: {
    page,
    search,
    limit: 10
  }
})

// Or with computed
const query = computed(() => ({
  page: page.value,
  search: search.value,
  limit: 10
}))

const { data } = await useFetch('/api/users', { query })
</script>
```

### Transform Data

```typescript
const { data: userNames } = await useFetch('/api/users', {
  transform: (users) => users.map(u => u.name)
})

// data.value is now string[] instead of User[]
```

### Pick Specific Fields

```typescript
const { data } = await useFetch('/api/user', {
  pick: ['id', 'name', 'email']  // Only these fields in payload
})
```

### useAsyncData - Custom Logic

```typescript
// Multiple parallel requests
const { data } = await useAsyncData('dashboard', async () => {
  const [users, posts, stats] = await Promise.all([
    $fetch('/api/users'),
    $fetch('/api/posts'),
    $fetch('/api/stats')
  ])
  return { users, posts, stats }
})

// Access: data.value.users, data.value.posts, data.value.stats
```

### Error Handling

```typescript
const { data, error, status } = await useFetch('/api/users')

// Check error
if (error.value) {
  console.error('Error:', error.value.message)
  console.error('Status:', error.value.statusCode)
}

// Status values: 'idle' | 'pending' | 'success' | 'error'
if (status.value === 'error') {
  showError(error.value)
}
```

### Manual Refresh

```typescript
const { data, refresh, execute } = await useFetch('/api/users', {
  immediate: false  // Don't fetch on mount
})

// Fetch manually
await execute()

// Refresh (re-fetch)
await refresh()

// Refresh with new params
await refresh({ dedupe: true })
```

### Shallow vs Deep Reactivity (v4 Change)

```typescript
// Nuxt 4 default: Shallow reactivity
const { data } = await useFetch('/api/user')
data.value.name = 'New Name'  // Won't trigger reactivity!

// Enable deep reactivity for mutations
const { data } = await useFetch('/api/user', {
  deep: true
})
data.value.name = 'New Name'  // Now works!

// Or refresh instead of mutating
const { data, refresh } = await useFetch('/api/user')
await $fetch('/api/user', { method: 'PATCH', body: { name: 'New Name' } })
await refresh()  // Re-fetch updated data
```

### Caching and Deduplication

```typescript
const { data } = await useFetch('/api/users', {
  key: 'users-list',           // Custom cache key
  dedupe: 'cancel',            // Cancel duplicate requests
  getCachedData: (key, nuxtApp) => {
    // Return cached data if valid
    return nuxtApp.payload.data[key]
  }
})
```

### Lazy Loading Data

```typescript
// useLazyFetch - Navigation happens immediately, data loads in background
const { data, pending } = useLazyFetch('/api/users')

// useLazyAsyncData
const { data, pending } = useLazyAsyncData('users', () => $fetch('/api/users'))
```

### $fetch - Client-Side Only

```typescript
// In event handlers (not during SSR)
const submitForm = async () => {
  const result = await $fetch('/api/submit', {
    method: 'POST',
    body: formData.value
  })
}

// In server routes
export default defineEventHandler(async (event) => {
  const externalData = await $fetch('https://api.example.com/data')
  return externalData
})
```

## State Management

### useState Patterns

```typescript
// Simple counter
const count = useState('count', () => 0)

// Complex object
const settings = useState('settings', () => ({

Related in General