Vue.js: crafting a universal composable is proving challenging

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.

Answer №1

After dedicating a significant amount of time to this project, I finally achieved what I was aiming for: implementing composable functionality to meet your specific needs.

The composable has undergone a few modifications; it now returns both a src and an isLoaded property. Admittedly, this may seem redundant at first glance since the src starts off as null and isLoaded remains false until the image finishes loading. Once loaded, src contains the image source URL, and isLoaded switches to true - a bit repetitive, isn't it?

Nevertheless,

Introducing the revamped 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 - featuring the alternative solution proposed

<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 - left unchanged

ViewRecipeDetail.vue - assuming that storeRecipe.data in this context is not an array, unlike the usage in ItemSwiper.vue. Additionally, based on the description provided, it appears that this represents a single recipe. It's worth noting the naming consistency across these components.

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

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Tips for having <script> update onchange instead of just onload

Is there a way to update the output of the <table id="mortgagetable"> each time a user changes the input values in the form? Currently, it only updates on load. Additionally, the content of the <div id="years" style="display:inline-block;">25 ...

The userName data is not being displayed in the console.log when using socket.io

I'm currently in the process of developing a chat application using socket.io. My goal is to log the user's name when they join the chat. I have set up a prompt on the client side to capture the user's input and emit it to the server. Howeve ...

"Adding dots" in a slideshow using HTML, CSS, and JS

I'm currently working on a slideshow that includes navigation dots at the bottom. Although the slideshow is functioning properly, I am encountering an issue where only one dot appears instead of the intended three in a row. Despite my research and att ...

Error: The dateOfEntrance property is null, causing an uncaught TypeError when trying to access the addEventListener method in Vue.js

One of the challenges I'm facing is comparing two dates (Entry and Exit) in a form to ensure that the release date comes before the exit date. The issue arises when I encounter an error message: Uncaught (in promise) TypeError: can't access prope ...

What is the best way to reset state in a React component when a key is pressed down while typing in an input

I am struggling with handling input submission in a React component when the Enter key is pressed. My component is built using components from material-ui. In the onKeyDown event handler, I try to clear the state by setting the only field in the componen ...

I'm curious about the potential vulnerabilities that could arise from using a Secret key as configuration in an express-session

Our code involves passing an object with a secret key's value directly in the following manner --> app.use(session({ secret: 'keyboard cat', resave: false, saveUninitialized: true, cookie: { secure: true } }) I am pondering wheth ...

Sending parameter and ng-click to the angular directive

I am looking for a way to transfer data from HTML to the directive, allowing the directive to be clickable using ng-click. How can I pass the parameter of the link function to the template? app.directive("hello", function(){ return { restrict: "E" ...

Having trouble retrieving PHP variable values using JavaScript?

<div id="div01">01</div> <div id="div02">02</div> <img src="../img/logo.png" onclick="blueSky()"/> js function blueSky() { $.ajax({ type:'GET', url: 'test.php', success: function(respond) { document.ge ...

The function's name has been obscured by the name of its parameter

Caution: ECMAScript 5 (ES5) strictly prohibits the use of arguments.callee(). To avoid this, either name function expressions or opt for a function declaration that calls itself. [MDN] How can we refer to the o function within itself in this scenario? fun ...

Manage AJAX API responses in Vuex by loading, storing, and reloading them for improved efficiency

In my project, I have a vue3-app that acts as the frontend for an asp.net core 2 api. Many components use the same data, leading to multiple identical requests. To avoid this, I want to store response data in vuex if it's not already there. The chal ...

The height of the browser action will not return to its original state

I'm currently working on an extension that provides responses based on user text input. However, I'm running into an issue where the height of the browser action won't reset properly. I've tried various methods to fix this problem, in ...

"Upon setting the state in React, the Full Calendar refreshes and retrieves events

My current setup involves using the FullCalendar API to fetch events as shown below: <FullCalendar ref={calendarRef} plugins={[listPlugin, bootstrap5Plugin]} initialView='listMonth' ...

The JavaScript fetch API failed to receive a response after sending data via a 'POST' request to a Django server

Currently, I am in the process of developing a poll application that utilizes both Django and React. My approach involves using the fetch API to send POST requests to my Django server and receive detailed information in return for further processing. For a ...

Conceal an absolutely positioned element outside its relatively positioned parent

I have a relative position parent element, <div style="position:relative" id="a"> and within that div, I'm creating some absolute positioned elements using jQuery, <div style="position:absolute;right:0" id="b"> These elements are anima ...

JavaScript module encounters an uncaught error: Attempting to assign a value to a constant variable

In another module, I defined a variable in the following manner: // module1.js let directory; export { directory }; Now, I am trying to access it in a separate module like so: // module2.js import { directory } from '../js/module1.js'; directo ...

Developing an interactive Breadcrumb component using Vue.js in the latest version, Vue 3

Struggling to develop a dynamic Breadcrumb-Component in Vue.js 3. After hours of research, I haven't found a suitable solution due to outdated methods or lack of flexibility. As a beginner in frontend development, I am unsure about the best library to ...

Searching for the precise draggable grid line position in rulerguides.js - tips and tricks!

Currently, I am utilizing rulerguides.js in my project. I have customized it for a specific div to display rulers and grid lines. You can refer to this FIDDLE. The rulers are functioning properly, but the draggable grid lines are being calculated based on ...

Implementing custom CSS styles for HighCharts API pie chart drilldown labels

I successfully created a pie chart using highcharts and configured the chart with the following options: chart: { type: 'pie', }, In order to change the width of the text on the chart, I added the following options which force e ...

What is the best way to capture the inputs' values and store them accurately in my object within localStorage?

Is there a more efficient way to get all the input values ​​and place them in the appropriate location in my object (localStorage) without having to individually retrieve them as shown in the code below? Below is the function I currently use to update ...

The background size cover feature is not functioning properly on mobile devices, especially on longer pages

In my Next.js project, I am encountering an issue with the background image not displaying on longer pages when viewed on a Google Pixel 3a device. Here is how it looks on shorter pages that do not exceed the viewport height: https://i.sstatic.net/jJBTy.pn ...