I need to make a composable universal.
Here's my case, I have 3 components - ItemSwiper, ItemCard and ViewRecipeDetail.
ItemSwiper contains card slides and it is the parent of ItemCard.
ItemSwiper has a loop of recipes:
<Slide v-for="recipe in storeRecipe.data" :key="recipe.recipe_id">
<ItemCard :data="recipe"/>
</Slide>
Here I'm passing the data prop to ItemCard.
Then, in the ItemCard I use this prop to display the information:
<template>
// here I'll add a skeleton loader that will be shown when 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 need to use this getSrc function and image preload in the onMounted hook in another component - ViewRecipeDetail, which isn't related to these two. In ViewRecipeDetail will be displayed the detailed information about a recipe.
I was thinking of moving this function and a hook into composable useRecipe and then use this composable in ItemCard and in ViewRecipeDetail.
However, due to the fact that in ItemSwiper I pass the data prop, which has recipe as its value, i.e. recipe in a loop, if I pass this prop as a parameter like this:
import { useRecipe } from '@/composable/useRecipe';
const props = defineProps(['data', 'pending']);
const { isLoaded, getSrc } = useRecipe(props.data);
Then in useRecipe we can use it like this:
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 will work for ItemCard, but it won't work for ViewRecipeDetail. Because I don't need any loop in ViewRecipeDetail. All I need to do is go to that recipe detail page and see the relevant information for that particular recipe.
It turns out that useRecipe is not universal now. We pass props.data as a parameter and it works for ItemCard, but it won't work for ViewRecipeDetail, because we need storeRecipe.data instead of props.data.
And here's ViewRecipeDatail. Please, tell me if I'm doing something wrong. I want to display an image same as I did in ItemCard, using a composable, but without 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>
Please, give me a possible solution. (If you don't understand something, please, let me know).
I spent too much time on this :p but I wanted to see if composable was possible for what you want to do
There are a few changes to the composable, it returns a
srcandisLoaded(though, that's a bit redundant assrcisnullandisLoadedisfalseuntil the image loads, at which pointsrchas the href, andisLoadedbecomes true - you can see the redunancy there.Anyway
The composable,
useRecipe.jsItemCard.vue- changed for the alternative I proposeItemSwiper.vue- unchangedViewRecipeDetail.vue- I'm assumingstoreRecipe.datahere is NOT an Array, and not the same asstoreRecipe.datainItemSwiper.vue, since you don't treat it like an array in your code! Also, your description suggests this is a single recipe. Not sure why the variable name is the same as in ItemSwiper though.