I am faced with the challenge of making a universal composable.
In my scenario, I have 3 components - ItemSwiper
, ItemCard
, and ViewRecipeDetail
.
ItemSwiper
contains card slides and serves as the parent of ItemCard
.
The loop of recipes in ItemSwiper
looks like this:
<Slide v-for="recipe in storeRecipe.data" :key="recipe.recipe_id">
<ItemCard :data="recipe"/>
</Slide>
Here, I pass the data prop to ItemCard
.
Subsequently, within ItemCard
, I utilize this prop to display the information:
<template>
// A skeleton loader will be added here to be displayed while the image is loading.
<img class="card__image" :src="getSrc('.jpg')" :alt="data.alt" />
</template>
<script setup>
const props = defineProps(['data', 'pending']);
const isLoaded = ref(false);
const getSrc = ext => {
return new URL(
`../assets/images/content/recipe/${props.data.image}${ext}`,
import.meta.url
).href;
};
onMounted(() => {
const img = new Image(getSrc('.jpg'));
img.onload = () => {
isLoaded.value = true;
};
img.src = getSrc('.jpg');
});
</script>
I now find myself needing to include the getSrc
function and image preloading logic used in the onMounted
hook within another component called ViewRecipeDetail
, which is independent of the aforementioned components. This particular component will display detailed information about a recipe.
My initial thought was to abstract this function and hook into a composable named useRecipe
for reusability in both ItemCard
and ViewRecipeDetail
.
However, due to the nature of passing the data
prop in ItemSwiper
, where it holds the value of recipe
from the loop, if passed as a parameter like so:
import { useRecipe } from '@/composable/useRecipe';
const props = defineProps(['data', 'pending']);
const { isLoaded, getSrc } = useRecipe(props.data);
We can leverage it within the useRecipe
composable as follows:
import { onMounted, ref } from 'vue';
export function useRecipe(data) {
const isLoaded = ref(false);
const getSrc = ext => {
return new URL(
`../assets/images/content/recipe/${data.image}${ext}`,
import.meta.url
).href;
};
onMounted(() => {
const img = new Image(getSrc('.jpg'));
img.onload = () => {
isLoaded.value = true;
};
img.src = getSrc('.jpg');
});
return {
isLoaded,
getSrc,
};
}
This approach works seamlessly for ItemCard
, however, it poses issues for ViewRecipeDetail
since there is no need for a loop within that component. The primary task in ViewRecipeDetail
is to navigate to the detailed page of a selected recipe and view relevant information pertaining to that specific recipe.
It appears that the current implementation of useRecipe
is not as universally applicable as desired. While passing props.data
as a parameter functions well for ItemCard
, it fails for ViewRecipeDetail
where storeRecipe.data
is needed instead.
Now let's take a closer look at the code for ViewRecipeDetail
. Kindly guide me if I am missing something crucial. I aim to showcase an image similar to how it is done in ItemCard
using a composable but without utilizing a prop:
<template>
<img
class="card__image"
:src="getSrc('.jpg')"
:alt="storeRecipe.data.alt" />
<div class="card__content">
<h2 class="card__title">
{{ storeRecipe.data.title }}
</h2>
<p class="card__text">
{{ storeRecipe.data.short_description }}
</p>
</div>
</template>
<script setup>
import { useStoreRecipe } from '@/store/storeRecipe';
import { useRecipe } from '@/composable/useRecipe';
const storeRecipe = useStoreRecipe();
const { getSrc } = useRecipe(storeRecipe.data);
</script>
I would greatly appreciate your insights regarding a potential solution to address this predicament. Should any part require further clarification, please do not hesitate to reach out.