skip to Main Content

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).

2

Answers


  1. It’s really hard to understand your question ,you just say won’t work,but how it’s won’t work,what’s the error message,if you had debug the code? can you show code on codepen that i can run it

    Login or Signup to reply.
  2. 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 src and isLoaded (though, that’s a bit redundant as src is null and isLoaded is false until the image loads, at which point src has the href, and isLoaded becomes true – you can see the redunancy there.

    Anyway

    The composable, useRecipe.js

    import { ref, toValue, watchEffect } from "vue";
    
    export function useRecipe(data, ext) {
      const isLoaded = ref(false);
      const src = ref(null);
    
      const getSrc = () => {
        isLoaded.value = false;
        const imgvalue = toValue(data)?.image;
        if (imgvalue) {
          const url = new URL(`../assets/images/content/recipe/${imgvalue}${ext}`, import.meta.url).href;
          const img = new Image();
          img.onload = () => {
            src.value = url;
            isLoaded.value = true;
          };
          img.src = url;
        }
      };
    
      watchEffect(() => {
        getSrc();
      });
      return {
        isLoaded,
        src,
      };
    }
    

    ItemCard.vue – changed for the alternative I propose

    <script setup>
    import { useRecipe } from "@/composables/useRecipe";
    const props = defineProps(["data"]);
    const { src, isLoaded } = useRecipe(props.data, ".jpg");
    </script>
    <template>
      <img class="card__image" :src="src" :alt="data.alt" />
    </template>
    

    ItemSwiper.vue – unchanged

    ViewRecipeDetail.vue – I’m assuming storeRecipe.data here is NOT an Array, and not the same as storeRecipe.data in ItemSwiper.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.

    <script setup>
    import { useStoreRecipe } from '@/store/storeRecipe';
    import { useRecipe } from "@/composables/useRecipe";
    const storeRecipe = useStoreRecipe();
    const { src, isLoaded } = useRecipe(storeRecipe.data, ".jpg");
    </script>
    
    <template>
      <img class="card__image" :src="src" :alt="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>
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search