Skip to content

Dominando useAsyncData en Nuxt 4 ​

Control Avanzado de Datos Asíncronos

¿Qué es useAsyncData? ​

useAsyncData es un composable de Nuxt que proporciona control granular sobre la carga de datos asíncronos. A diferencia de useFetch que es más opiniado, useAsyncData te da más flexibilidad para personalizar cómo se obtienen y gestionan los datos.

Características clave: ​

  • ✅ Control manual sobre la función de fetching
  • ✅ Gestión de caché con keys personalizadas
  • ✅ Reactividad integrada con estados de carga y error
  • ✅ Reejecución programática de peticiones

Sintaxis Básica ​

ts
const { data, pending, error, refresh, execute } = await useAsyncData(
  key,           // Unique identifier for cache
  handler,       // Function that returns the data
  options        // Configuration options
);

Ejemplo práctico ​

vue
<script setup>
const { data: posts, pending, error } = await useAsyncData(
  'posts', // Unique key for cache
  () => $fetch('https://jsonplaceholder.typicode.com/posts')
);
</script>

<template>
  <div v-if="pending">Cargando posts...</div>
  <div v-else-if="error">Error: {{ error.message }}</div>
  <ul v-else>
    <li v-for="post in posts" :key="post.id">{{ post.title }}</li>
  </ul>
</template>

Diferencias Clave: useAsyncData vs useFetch ​

CaracterísticauseAsyncDatauseFetch
Flexibilidad✅ Alta (control manual)❌ Media (opinión de Nuxt)
SintaxisMás verbosaMás concisa
Key de cachéRequeridaAutomática (basada en URL)
Función handlerRequeridaImplícita
Uso recomendadoLógica compleja de fetchingCasos simples y rápidos

Opciones de Configuración Avanzadas ​

lazy - Carga diferida ​

ts
const { data, pending } = await useAsyncData(
  'posts',
  () => $fetch('https://jsonplaceholder.typicode.com/posts'),
  { lazy: true } // Does not block navigation
);

default - Valor por defecto ​

ts
const { data: user } = await useAsyncData(
  'user',
  () => $fetch('https://jsonplaceholder.typicode.com/users/1'),
  { default: () => ({ name: 'Usuario por defecto' }) }
);

transform - Transformar datos ​

ts
const { data: posts } = await useAsyncData(
  'formatted-posts',
  () => $fetch('https://jsonplaceholder.typicode.com/posts'),
  {
    transform: (posts) => posts.map(post => ({
      ...post,
      title: post.title.toUpperCase(),
      createdAt: new Date().toISOString()
    }))
  }
);

watch - Reacción a cambios ​

ts
const userId = ref(1);

const { data: user } = await useAsyncData(
  'user',
  () => $fetch(`https://jsonplaceholder.typicode.com/users/${userId.value}`),
  { watch: [userId] } // Reruns when userId changes
);

Patrones Avanzados con useAsyncData ​

Combinación con $fetch para máximo control ​

vue
<script setup>
const page = ref(1);
const limit = ref(10);

const { data: posts, pending, refresh } = await useAsyncData(
  `posts-page-${page.value}`,
  async () => {
    const response = await $fetch('https://jsonplaceholder.typicode.com/posts', {
      params: { _page: page.value, _limit: limit.value }
    });
    
    // Complex additional logic
    const transformedPosts = response.map(post => ({
      ...post,
      excerpt: post.body.substring(0, 100) + '...',
      isFeatured: post.id % 5 === 0
    }));
    
    return transformedPosts;
  },
  { watch: [page, limit] } // Reacts to changes
);
</script>

Paginación con refresh y execute ​

vue
<script setup>
const page = ref(1);
const perPage = 10;

const { data: posts, execute } = await useAsyncData(
  'paginated-posts',
  () => $fetch('https://jsonplaceholder.typicode.com/posts', {
    params: { _page: page.value, _limit: perPage }
  }),
  { immediate: false } // It does not run automatically
);

// Change page
const goToPage = (newPage) => {
  page.value = newPage;
  execute(); // Rerun with new parameters
};
</script>

Manejo de errores granular ​

vue
<script setup>
const { data: posts, error } = await useAsyncData(
  'posts-with-error-handling',
  async () => {
    try {
      const data = await $fetch('https://jsonplaceholder.typicode.com/posts');
      return data;
    } catch (err) {
      // Custom error handling
      console.error('Error fetching posts:', err);
      throw createError({
        statusCode: 500,
        message: 'No se pudieron cargar los posts'
      });
    }
  }
);
</script>

Múltiples solicitudes paralelas ​

vue
<script setup>
const { data: combinedData } = await useAsyncData(
  'dashboard-data',
  async () => {
    const [posts, users, comments] = await Promise.all([
      $fetch('https://jsonplaceholder.typicode.com/posts'),
      $fetch('https://jsonplaceholder.typicode.com/users'),
      $fetch('https://jsonplaceholder.typicode.com/comments')
    ]);
    
    return { posts, users, comments };
  }
);
</script>

Integración con Estado Global ​

ts
// composables/useAuthData.ts
export const useAuthData = () => {
  const auth = useAuthStore();
  
  const { data: userProfile, refresh } = await useAsyncData(
    `user-profile-${auth.userId}`,
    () => $fetch(`/api/users/${auth.userId}/profile`),
    { server: false } // Only runs on client
  );
  
  return { userProfile, refresh };
};

Optimización de Rendimiento ​

Deduplicación de peticiones ​

ts
// Same key = same cache
const { data: post1 } = await useAsyncData('post-1', () => 
  $fetch('https://jsonplaceholder.typicode.com/posts/1')
);

// In another component - use cache instead of new request
const { data: cachedPost } = useNuxtData('post-1');

Prefetching de datos ​

ts
// Pre-load data for navigation
const prefetchPosts = async () => {
  await useAsyncData('prefetched-posts', 
    () => $fetch('https://jsonplaceholder.typicode.com/posts'),
    { immediate: false }
  );
};

// Use in hover or anticipation
onMounted(() => {
  const link = document.querySelector('#posts-link');
  link.addEventListener('mouseenter', prefetchPosts);
});

Migración desde useFetch a useAsyncData ​

De esto:

ts
const { data: posts } = await useFetch(
  'https://jsonplaceholder.typicode.com/posts'
);

A esto:

ts
const { data: posts } = await useAsyncData(
  'posts',
  () => $fetch('https://jsonplaceholder.typicode.com/posts')
);

Ejemplo Completo: Dashboard con Múltiples Datos ​

vue
<script setup>
const filters = reactive({
  category: 'all',
  sortBy: 'date',
  search: ''
});

const { data: dashboardData, pending, refresh } = await useAsyncData(
  'dashboard',
  async () => {
    const [posts, stats, notifications] = await Promise.all([
      $fetch('https://jsonplaceholder.typicode.com/posts'),
      $fetch('/api/stats'),
      $fetch('/api/notifications')
    ]);
    
    // Complex filtering logic
    const filteredPosts = posts.filter(post => {
      if (filters.category !== 'all' && post.category !== filters.category) {
        return false;
      }
      if (filters.search && !post.title.includes(filters.search)) {
        return false;
      }
      return true;
    });
    
    return { posts: filteredPosts, stats, notifications };
  },
  { watch: [() => filters.category, () => filters.search] }
);
</script>

Evita keys genéricas como data o response ​

¿Por qué? ​

Las keys en useAsyncData son identificadores únicos para el sistema de caché de Nuxt. Si usas keys genéricas, puedes tener conflictos y sobrescribir datos accidentalmente.

Ejemplo de lo que NO debes hacer ​

ts
// ❌ BAD - Key too generic
const { data: posts } = await useAsyncData(
  'data', // Too generic
  () => $fetch('/api/posts')
);

const { data: users } = await useAsyncData(
  'data', // Same key! Overwrites the post cache.
  () => $fetch('/api/users')
);

Ejemplo de lo que SÍ debes hacer ​

ts
// ✅ GOOD - Specific and descriptive keys
const { data: posts } = await useAsyncData(
  'posts-list', // Specific for posts
  () => $fetch('/api/posts')
);

const { data: users } = await useAsyncData(
  'active-users', // Specific for users
  () => $fetch('/api/users')
);

Buenas prácticas para naming de keys ​

ts
// ✅ Use descriptive and unique names
'user-profile-123'
'blog-posts-page-2'
'dashboard-stats-2024'
'products-category-electronics'

// ✅ Include unique identifiers where necessary
const userId = 123;
await useAsyncData(`user-${userId}-posts`, () => fetchUserPosts(userId));

// ✅ Use consistent naming
'posts-recent'    // For recent posts
'posts-popular'   // For popular posts
'posts-by-category-tech' // For tech category posts

Ventajas de usar keys específicas ​

  1. ✅ Evita conflictos de caché
  2. ✅ Permite reutilizar datos con useNuxtData()
  3. ✅ Mejora la depuración (sabes qué datos estás viendo)
  4. ✅ Facilita el mantenimiento del código

Ejemplo de reutilización con keys específicas ​

ts
// In a component
const { data: posts } = await useAsyncData(
  'featured-posts',
  () => $fetch('/api/posts/featured')
);

// In another component - Reuses existing cache
const { data: cachedPosts } = useNuxtData('featured-posts');

Mejores Prácticas con useAsyncData ​

  1. ✅ Usa keys descriptivas para el caché
  2. ✅ Combina con $fetch para máximo control
  3. ✅ Usa watch para reactividad avanzada
  4. ✅ Maneja errores dentro del handler
  5. ✅ Prefiere useAsyncData sobre useFetch cuando necesites lógica compleja
  6. ❌ Evita keys genéricas como data o response

Conclusión ​

useAsyncData es tu herramienta de nivel intermedio para:

  • ✅ Control granular sobre el fetching de datos
  • ✅ Lógica compleja de transformación y combinación
  • ✅ Gestión avanzada de caché con keys personalizadas
  • ✅ Integración con estado global y stores

¿Siguiente paso? useNuxtData para aprender a manejar y reutilizar el caché de forma eficiente.