I have a project which is build in Nuxt 3 with a homepage which shows products. On that homepage, you can click on the title of the product and the product details page opens up. I archive this by using <NuxtLink>
<NuxtLink :to="{ name: 'slug', params: { slug: product.slug }}">
<p>{{ product.title }}</p>
</NuxtLink>
When the user click on that link, the [slug].vue
component will show the product details page. So far, so good. However, this leads to the [slug].vue
component fetching the product data from the API again using its slug like this:
const { data } = await useFetch(runtimeConfig.public.api.product.view + `/${slug}`);
This behavior is also needed when the user visits the product details page directly from a link he saw on e.g. Facebook etc. But when the user clicks on the <NuxtLink>
from the homepage, I would like to pass my product object to the [slug].vue
component, as the homepage already has all needed information about the product the [slug].vue
component needs to render.
If I will be able to pass my product object to the [slug].vue
component, I could skip the extra API fetch and present the data right away to the user, increasing page speed and user experience.
I have already written the necessary code I need within my [slug].vue
component to determine whether the props are empty and the component needs to fetch them from the API or the component can just use the props being passed and skip the fetch:
<script setup lang="ts">
const { slug } = useRoute().params;
const runtimeConfig = useRuntimeConfig()
const route = useRoute()
// Define component props
const props = defineProps({
product: null
});
// Computed property to determine whether data needs to be fetched
const fetchData = computed(() => {
return !props.product; // If props.data is not provided, fetch data
});
// Data ref to store fetched data
const product = ref(null);
// Fetch data from the API if fetchData is true
if (fetchData.value) {
const { data } = await useFetch(runtimeConfig.public.api.product.view + `/${slug}`);
product.value = data.value;
}
</script>
However, I am failing at passing my product object from my <NuxtLink>
to the props of my [slug].vue
component. How can I archive that?
I do not want to pass my product object from my homepage to my [slug].vue
component using params
. This will not work as the product object is quite long. I could stringify it with JSON.stringify()
but I also don't want to pass data via my URL except the slug. Furthermore, the URL would also look very ugly, passing a very long JSON
string to it.
I also do not want to use a storage like Pinia. I want to make use of the available props
functionality, which was build to pass data between components.
If you have any other idea, how I can pass the data to my [slug].vue
component from the homepage, let me know.
Kind regards
This used to be possible with params
, but as of this change in vue router, it's not possible to do it without adding it as url query or slug, because this is considered as an anti-pattern.
As noted in the link above, there are alternatives to achieve this, and personally i'd recommend to use the localStorage
or vue store
approach, but since you stated specifically that you don't want that, you can use history state as suggested in the same link above. A simple implementation would be adding state
to the :to
props of NuxtLink
:
// homepage /index
<template>
<div>
<NuxtLink :to="{ name: 'slug', state: { product: toRaw(product) }}">
<p>{{ product.title }}</p>
</NuxtLink>
</div>
</template>
<script setup>
const product = ref({
title: 'product title',
slug: 'product123'
})
</script>
Then:
// product detail page /slug
<template>
<section>
<h1>{{ product?.title }}</h1>
</section>
</template>
<script setup>
const product = ref(null)
onMounted(() => {
product.value = history.state.product // get product from previous route
if (!product.value) {
// fetch product from API if it doesn't exist
}
})
</script>
We need to pass a non-reactive object to state
, so we can use toRaw
like the above code to convert the reactive state to normal object. Then on the product detail page, you can access the data with
history.state.product
This approach, however, will persist the state when the page is reloaded. If you want to clear the state when you reload, you can add these additional codes at the end of the onMounted
of the product detail page:
// product detail page /slug
onMounted(() => {
product.value = history.state.product // get product from previous route
if (!product.value) {
// fetch product from API if it doesn't exist
}
const { product: _product, ...currentState } = history.state // get only other keys than product key
window.history.replaceState({ ...currentState }, '') // remove state
})
I will say again that it's better to use other approach than this as suggested by the vue router change.