Home > Software engineering >  How to load an async prop value inside a Vue "v-for" element?
How to load an async prop value inside a Vue "v-for" element?

Time:08-28

I am using Vue with Firebase Firestore.

I have a list of people displayed using a v-for element. The list of people is of an unknown size. Data for the list comes from Firestore which arrives asynchronously. This part is easy because I can get the data inside an onMounted hook or setup a Firestore listener.

But for each item in the v-for I want to make another async call to Firestore to fetch each of their images (the image urls are stored in a separate Firestore document). And I have no idea how to handle this part.

Currently, the images are all broken because getPersonImageById(person.id) is asynchronous. Therefore it returns undefined immediately, and later delivers the image url. And if I try to load the prop value using await like so <img :src="await getPersonImageById(person.id)" /> I get this error:

Error parsing JavaScript expression: 'await' is only allowed within async functions and at the top levels of modules. (1:1)

Here is a simplified version of my code:

<template>
  <div v-for="(person, index) in people" :key="person.id">
    {{ person.name }}
    <img :src="getPersonImageById(person.id)" />
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue';

// Simulate async database call (i.e. Firestore)
const databaseAsyncFetch = async (data) => {
  await new Promise((resolve) => setTimeout(resolve, 2000));
  console.log(data);
  return data;
};

// Load the people data
const people = ref();
onMounted(async () => {
  people.value = await databaseAsyncFetch([
    { id: '1', name: 'John', fruit: 'Banana' },
    { id: '2', name: 'Mary', fruit: 'Apple' },
    { id: '3', name: 'Luke', fruit: 'Orange' },
  ]);
});

// Make another async call for each "person" inside the v-for to fetch the images
const getPersonImageById = async (id) => {
  await databaseAsyncFetch(
    'https://picsum.photos/100/100?random='   id   '.jpg'
  );
};
</script>

And here is a reproduction link: https://stackblitz.com/edit/vitejs-vite-ljqftl?file=src/App.vue

CodePudding user response:

Don't tie :src to a fetch. You should make a follow up fetch call after the first database fetch. Something like:

people.value = await databaseAsyncFetch([
    { id: '1', name: 'John', fruit: 'Banana' },
    { id: '2', name: 'Mary', fruit: 'Apple' },
    { id: '3', name: 'Luke', fruit: 'Orange' },
  ]);
people.value.forEach(person => { person.image = getPersonImageById(person.id) })

Then conditionally render the <img> based on whether or not person.image exists

<img v-if="person?.image" :src="person.image" />
  • Related