Data & State
Developing a modern e-commerce application often requires fetching significant amounts of data for a single page, such as category details, product information, and potentially enriching this data by fetching from external sources. Efficiently managing these asynchronous operations and handling the resulting state across your application is crucial for performance and maintainability.
The SCAYLE Storefront Application, built on Nuxt 3, leverages Nuxt's powerful built-in composables like useAsyncData
to address these challenges. useAsyncData
is the recommended way to fetch asynchronous data in Nuxt 3, handling server-side rendering (SSR), client-side navigation, caching, error handling, and data hydration automatically.
Asynchronous Data Fetching with useAsyncData
The useAsyncData
composable takes an asynchronous handler function where you place your data fetching logic. This handler function is executed on the server during SSR and on the client during navigation. Within this handler, you have full control over how multiple fetch calls are executed: serially, in parallel, or a combination.
const { data, pending, error, refresh } = useAsyncData(
'unique-fetch-key', // Required key for caching/hydration
async () => {
// Your data fetching logic goes here
// e.g., calling RPC methods, fetching from other APIs
return resultOfTheFetch
}
)
For Storefront Composables, the key
is typically auto-generated internally. You might need to pass it manually, for instance, to simplify debugging of _asyncData
or when using useNuxtData
.
Serial Execution within useAsyncData
Place your fetch calls using await
inside the useAsyncData
handler to execute them sequentially. This is necessary when a subsequent fetch call depends on the result of a previous one.
const { data, pending, error } = useAsyncData('serialFetchExample', async () => {
const resultA = await fetchA() // Assume fetchA() returns a Promise
// fetchB depends on resultA
const resultB = await fetchB(resultA) // Assume fetchB() returns a Promise
return { resultA, resultB } // Return the combined data
});
While this approach ensures the correct order of execution, it can potentially increase overall page loading times due to the sequential nature of the requests.
Parallel Execution within useAsyncData
If your data fetch calls are independent, use Promise.all
within the useAsyncData
handler to execute them in parallel. This can significantly reduce the total loading time.
const { data, pending, error } = useAsyncData('parallelFetchExample', async () => {
const [resultA, resultB, resultC] = await Promise.all([
fetchA(), // Assume fetchA() returns a Promise
fetchB(), // Assume fetchB() returns a Promise
fetchC() // Assume fetchC() returns a Promise
])
return { resultA, resultB, resultC } // Return the combined data
})
This pattern initiates all fetch calls simultaneously and waits for all of them to complete before returning the final data.
Mixed Execution within useAsyncData
You can combine serial and parallel execution patterns within the useAsyncData
handler. For example, you might start some independent fetches while waiting for others that have dependencies.
const { data, pending, error } = useAsyncData('mixedFetchExample', async () => {
// Start fetchC immediately - this runs in parallel with the A/B sequence
const promiseC = fetchC() // Assume fetchC() returns a Promise
// Execute fetchA and fetchB serially
const resultA = await fetchA() // Assume fetchA() returns a Promise
const resultB = await fetchB(resultA) // Assume fetchB() returns a Promise
// Wait for the parallel fetchC to complete
const resultC = await promiseC
// Return the combined results
return { resultA, resultB, resultC }
});
This allows for fine-grained control over your data fetching strategy, balancing dependencies with performance within a single useAsyncData
call.
Shared State and Caching with the useAsyncData
Key
The useRpc
composable, used by many Storefront composables, internally also relies on shared state behavior that can be manually disabled:
One of the most powerful features of useAsyncData
is its built-in state management and caching based on the unique key
provided as its first argument. This key is used by Nuxt's internal state tree, allowing the state (data, pending, error) associated with a specific key to be shared across your application.
This key-based sharing is crucial for SSR and hydration. Data fetched on the server using useAsyncData
with a specific key is automatically serialized and sent to the client. When useAsyncData
is called on the client with the same key, Nuxt rehydrates the state from the server payload instead of refetching the data, ensuring a smooth transition and immediate reactivity.
SCAYLE Storefront composables, including those that wrap RPC methods, are designed to leverage useAsyncData
and this key-based sharing mechanism.
Consider a useNews
composable (which internally uses useAsyncData
):
// In ComponentA
const newsA = useNews('my-news-section') // Calls useAsyncData('my-news-section', ...)
// In ComponentB
const newsB = useNews('my-news-section') // Calls useAsyncData('my-news-section', ...)
// Let's assume ComponentA or a parent component triggers the fetch
// e.g., await newsA.fetch() if useNews provides a fetch method wrapping useAsyncData's execute/refresh,
// or Nuxt automatically fetches on SSR/navigation.
// After the fetch associated with 'my-news-section' key completes:
console.log("NewsA data", newsA.data.value) // Will output data from the fetch
console.log("NewsB data", newsB.data.value) // Will output the *same* data as newsA.data.value
console.log(newsA.data.value === newsB.data.value) // => true
In this scenario, both newsA
and newsB
instances returned by useNews
point to the same reactive data state managed by useAsyncData
under the 'my-news-section'
key. This is a fundamental pattern: you can call the same composable in multiple components, but the underlying data fetching and state management for a given key happen only once per request/navigation, and all instances using that key access the shared state.
Preventing State Sharing
If you require independent state for different instances of the same composable or for data that should not be shared via the key, provide unique keys or omit the key (though omitting the key disables caching and is generally not recommended for fetched data). Removing the key
will prevent sharing data and trigger independent calls for the usage of a useAsyncData
instance without request deduping. As Storefront Composables automatically generate unique key
internally, hydration will still work as expected between server and client.
// In ComponentA
const newsA = useNews('news-section-A') // Calls useAsyncData('news-section-A', ...)
// In ComponentB
const newsB = useNews('news-section-B') // Calls useAsyncData('news-section-B', ...)
// After fetch associated with 'news-section-A' completes:
console.log("NewsA data", newsA.data.value) // Will output data from its fetch
// Data for 'news-section-B' hasn't been fetched yet (unless triggered elsewhere)
console.log("NewsB data", newsB.data.value) // Will likely be null or initial state
console.log(newsA.data.value === newsB.data.value) // => false (unless both are null/undefined)
By providing distinct keys, useAsyncData
treats them as separate data sources, managing their state, caching, and hydration independently.
Understanding how useAsyncData
's key facilitates shared state and integrates with Nuxt's SSR and hydration is crucial for effectively handling data and building performant components in the SCAYLE Storefront Application.
More details can be found in the official Nuxt "Data Fetching" Guide or the "useAsyncData vs. useFetch" YouTube video by Alexander Lichter, a Core Maintainer of Nuxt.