N
Nuxt2mo ago
çqck

Problems with loading/rendering a list of items when switching pages

On the products page, I implemented product loading and a "Load More" button. This is done using a ref variable page and useLazyAsyncData with the parameter watch: [page]. Since the data returned by useLazyAsyncData contains only new items, and I need to append these to the list, I created another ref variable called products, where I concatenate the items from the data. I do this using a watch: watch(data, (newData) => { if (newData && newData.products) { products.value = products.value.concat(newData.products); } }, { immediate: true }); The problem is that if we navigate to the /products page, then to another page, and then back to /products, the old list of items remains on the page. I don’t understand where it’s coming from and whether I’m doing everything correctly. Could you please advise how to resolve this issue? Here’s a link to the demonstration: https://stackblitz.com/edit/nuxt-starter-petfxh7x?file=pages%2Fproducts.vue
arbitino
StackBlitz
Nuxt - Starter (forked) - StackBlitz
Create a new Nuxt project, module, layer or start from a theme with our collection of starters.
5 Replies
kapa.ai
kapa.ai2mo ago
Hi @çqck. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- The issue you're experiencing is related to how Nuxt handles caching and state management when navigating between pages. Let's break down the problem and provide a solution based on the information from the knowledge sources. The main reason for this behavior is that Nuxt caches the result of useLazyAsyncData by default. When you navigate away from the page and then back, it's using the cached data instead of fetching new data. To resolve this issue, you can try the following approaches: 1. Use a unique key for useLazyAsyncData: Add a unique key to your useLazyAsyncData call. This will ensure that Nuxt treats each request as unique:
const { data, refresh } = await useLazyAsyncData(
`products-${page.value}`,
() => $fetch('/api/products', {
params: {
page: page.value
}
}),
{
watch: [page]
}
)
const { data, refresh } = await useLazyAsyncData(
`products-${page.value}`,
() => $fetch('/api/products', {
params: {
page: page.value
}
}),
{
watch: [page]
}
)
2. Clear the data when leaving the page: You can use the clear function returned by useLazyAsyncData to reset the data when the component is unmounted. Add this to your component:
onUnmounted(() => {
clear()
})
onUnmounted(() => {
clear()
})
3. Use the refresh function when entering the page: You can call the refresh function when the component is mounted to ensure fresh data:
onMounted(() => {
refresh()
})
onMounted(() => {
refresh()
})
4. Disable caching: If you want to always fetch fresh data, you can disable caching by providing a getCachedData function that always returns null:
const { data, refresh } = await useLazyAsyncData(
'products',
() => $fetch('/api/products', {
params: {
page: page.value
}
}),
{
watch: [page],
getCachedData: () => null
}
)
const { data, refresh } = await useLazyAsyncData(
'products',
() => $fetch('/api/products', {
params: {
page: page.value
}
}),
{
watch: [page],
getCachedData: () => null
}
)
These solutions are based on the information from Docs > Api > Composables > useAsyncData and Docs > Api > Composables > useLazyAsyncData. Additionally, instead of using a separate products ref and a watcher, you could consider using a computed property to combine the existing products with the new ones:
const allProducts = computed(() => {
return data.value ? [...previousProducts, ...data.value.products] : previousProducts
})
const allProducts = computed(() => {
return data.value ? [...previousProducts, ...data.value.products] : previousProducts
})
This approach might be more reactive and easier to manage. Remember to handle the loading state appropriately, as useLazyAsyncData doesn't block navigation. You should display a loading indicator while the data is being fetched. By implementing these changes, you should be able to resolve the issue of old data persisting when navigating back to the products page. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2mo ago
<@651692284068495370> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
çqck
çqckOP2mo ago
@kapa.ai Write a more detailed example with computed, please, and explain how it works in this context.
kapa.ai
kapa.ai2mo ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- Certainly! I'll provide a more detailed example using a computed property in the context of your product listing page. This approach can help manage the state of your products more efficiently. Here's an expanded example:
<script setup>
import { ref, computed } from 'vue'
import { useLazyAsyncData } from '#app'

const page = ref(1)
const pageSize = 10

const { data, pending, refresh } = useLazyAsyncData(
() => $fetch('/api/products', {
params: {
page: page.value,
pageSize
}
}),
{
watch: [page]
}
)

// Store all fetched products
const allProducts = ref([])

// Computed property to combine existing and new products
const displayedProducts = computed(() => {
if (data.value && data.value.products) {
// Ensure we don't add duplicate products
const newProducts = data.value.products.filter(
newProduct => !allProducts.value.some(
existingProduct => existingProduct.id === newProduct.id
)
)
allProducts.value = [...allProducts.value, ...newProducts]
}
return allProducts.value
})

// Computed property for total number of products
const totalProducts = computed(() => data.value?.total || 0)

// Computed property to determine if there are more products to load
const hasMoreProducts = computed(() =>
displayedProducts.value.length < totalProducts.value
)

// Function to load more products
const loadMore = () => {
if (!pending.value && hasMoreProducts.value) {
page.value++
}
}

// Clear products when component is unmounted
onUnmounted(() => {
allProducts.value = []
})
</script>
<template>
<div>
<h1>Products</h1>
<div v-if="pending && !displayedProducts.length">Loading...</div>
<ul v-else>
<li v-for="product in displayedProducts" :key="product.id">
{{ product.name }}
</li>
</ul>
<button @click="loadMore" :disabled="!hasMoreProducts || pending">
{{ pending ? 'Loading...' : 'Load More' }}
</button>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { useLazyAsyncData } from '#app'

const page = ref(1)
const pageSize = 10

const { data, pending, refresh } = useLazyAsyncData(
() => $fetch('/api/products', {
params: {
page: page.value,
pageSize
}
}),
{
watch: [page]
}
)

// Store all fetched products
const allProducts = ref([])

// Computed property to combine existing and new products
const displayedProducts = computed(() => {
if (data.value && data.value.products) {
// Ensure we don't add duplicate products
const newProducts = data.value.products.filter(
newProduct => !allProducts.value.some(
existingProduct => existingProduct.id === newProduct.id
)
)
allProducts.value = [...allProducts.value, ...newProducts]
}
return allProducts.value
})

// Computed property for total number of products
const totalProducts = computed(() => data.value?.total || 0)

// Computed property to determine if there are more products to load
const hasMoreProducts = computed(() =>
displayedProducts.value.length < totalProducts.value
)

// Function to load more products
const loadMore = () => {
if (!pending.value && hasMoreProducts.value) {
page.value++
}
}

// Clear products when component is unmounted
onUnmounted(() => {
allProducts.value = []
})
</script>
<template>
<div>
<h1>Products</h1>
<div v-if="pending && !displayedProducts.length">Loading...</div>
<ul v-else>
<li v-for="product in displayedProducts" :key="product.id">
{{ product.name }}
</li>
</ul>
<button @click="loadMore" :disabled="!hasMoreProducts || pending">
{{ pending ? 'Loading...' : 'Load More' }}
</button>
</div>
</template>
Let's break down how this works: 1. We use useLazyAsyncData to fetch products, watching the page ref for changes. 2. We introduce an allProducts ref to store all fetched products across multiple page loads. 3. The displayedProducts computed property is the key to managing our product list: - It checks if new data has been fetched (data.value && data.value.products). - If new data exists, it filters out any duplicate products to avoid adding them twice. - It then updates allProducts.value with the new, unique products. - Finally, it returns allProducts.value, which now contains all unique products fetched so far. 4. We add two more computed properties: - totalProducts to keep track of the total number of products available from the API. - hasMoreProducts to determine if there are more products to load by comparing the number of displayed products to the total. 5. The loadMore function increments the page number if there are more products and no pending request. 6. In the onUnmounted hook, we clear allProducts to ensure a fresh start if the user navigates away and back. This approach offers several advantages: - It automatically handles the merging of new products with existing ones, preventing duplicates. - The computed properties reactively update the view whenever the underlying data changes. - It provides a clean way to determine when all products have been loaded. - By clearing the products list on unmount, we ensure a fresh start when returning to the page. This solution addresses the issue of persisting old data when navigating back to the products page, while also providing a more robust and reactive way of managing the product list state. Docs > Getting-started > Data Fetching provides more context on using useLazyAsyncData for client-side data fetching, which is particularly useful for this kind of paginated data loading scenario. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2mo ago
<@651692284068495370> Kapa.ai is still learning and improving, please let me know how I did by reacting below.

Did you find this page helpful?