Load Firestore data in components before rendering with Vuefire
<p>I'm trying to populate a people table with names and profile pictures. The name is derived from the Firestore database and is used by the image to find related images in the Firebase bucket. </p>
<p>I have been watching hours of videos and have gone through almost a hundred articles so my example code contains snippets of each code as I have been trying every combination and getting a mix but not Successful results. </p>
<p>In the current state which returns minimal errors, I am able to successfully populate the table with the names, however within the same component it fails to extract the profile picture. The value used for the profile picture was updated, but it was updated from a placeholder value to <code>undefined</code>. </p>
<p><strong>GamePlanner.vue</strong></p>
<pre class="brush:vue;toolbar:false;"><template>
<div>
<Field />
<Bench />
<!-- <Suspense>
<template #default> -->
<PlanSummary />
<!-- </template>
<template #fallback>
<div class="loading">Loading...</div>
</template>
</Suspense> -->
</div>
</template>
</pre>
<p><strong>PlanSummary.vue</strong></p>
<pre class="brush:vue;toolbar:false;"><template>
<div class="summaryContainer">
<div class="inningTableContainer">
<table class="inningTable">
<tbody>
<!-- <Suspense> -->
<!-- <template #default> -->
<PlayerRow v-for="player in players.value" :key="player.id" :player="(player as Player)" />
<!-- </template> -->
<!-- <template #fallback>
<tr data-playerid="0">
<img class="playerImage" src="https://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50" />
Loading players...
<span class="playerNumber">00</span>
</tr>
</template> -->
<!-- </Suspense> -->
</tbody>
</table>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, onErrorCaptured, ref } from "vue";
import { useFirestore, useCollection } from "vuefire";
import { collection } from "firebase/firestore";
import { Player, Inning } from "@/definitions/GamePlanner";
import PlayerRow from "./PlayerRow.vue";
const db = useFirestore();
const gamePlanID = "O278vlB9Xx39vkZvIsdP";
// const players = useCollection(collection(db, `/gameplans/${gamePlanID}/participants`));
// const players = ref(useCollection(collection(db, `/gameplans/${gamePlanID}/participants`)));
// Unhandled error during execution of scheduler flush
// Uncaught (in promise) DOMException: Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.
const players = ref();
players.value = useCollection(collection(db, `/gameplans/${gamePlanID}/participants`));
// Seeminly infinite loop with "onServerPrefetch is called when there is no active component instance to be associated with."
// Renders 5 (??) undefined players
// One error shown: Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'substring')
// const players = computed(() => useCollection(collection(db, `/gameplans/${gamePlanID}/participants`)));
// onErrorCaptured((error, vm, info) => {
// console.log("Error loading Summary component: ", error, "vm: ", vm, "info: ", info);
// throw error;
// });
</script>
</pre>
<p><strong>PlayerRow.vue</strong></p>
<pre class="brush:vue;toolbar:false;"><template>
<tr :key="player2.id" :data-playerid="player2.id">
<td>
<img class="playerImage" :src="playerPictureURL" />
{{ player2.nickname || player2.firstName " " player2.lastName }}
<span class="playerNumber">{{ player2.playerNumber }}</span>
</td>
</tr>
</template>
<script lang="ts" setup>
import { ref, PropType, computed, onMounted, watch } from "vue";
import { useFirebaseStorage, useStorageFileUrl } from "vuefire";
import { ref as storageRef } from 'firebase/storage';
import { Player, Inning } from "@/definitions/GamePlanner";
const fs = useFirebaseStorage();
const props = defineProps({
'player': { type: Object as PropType<Player>, required: true },
// 'innings': Array<Inning>
});
const player2 = ref(props.player);
// const innings = computed(() => props.innings);
// const playerPictureURL = computed(() => {
// const playerPictureFilename = `${player2.value.firstName.substring(0,1)}${player2.value.lastName}.png`.toLowerCase();
// const playerPictureResource = storageRef(fs, `playerPictures/${playerPictureFilename}`);
// return useStorageFileUrl(playerPictureResource).url.value as string;
// });
// const playerPictureURL = ref(() => {
// const playerPictureFilename = `${player2.value.firstName.substring(0,1)}${player2.value.lastName}.png`.toLowerCase();
// const playerPictureResource = storageRef(fs, `playerPictures/${playerPictureFilename}`);
// return useStorageFileUrl(playerPictureResource).url.value as string;
// });
const playerPictureURL = ref("https://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50");
async function getPlayerPictureURL() {
console.log("PlayerRow.ts getPlayerPictureURL");
const playerPictureFilename = `${player2.value.firstName.substring(0,1)}${player2.value.lastName}.png`.toLowerCase();
const playerPictureResource = await storageRef(fs, `playerPictures/${playerPictureFilename}`);
playerPictureURL.value = await useStorageFileUrl(playerPictureResource).url.value as string;
}
onMounted(() => {
console.log("PlayerRow.ts onMounted");
getPlayerPictureURL();
});
watch(playerPictureURL, (newVal, oldVal) => {
console.log("PlayerRow.ts watch playerPictureURL");
console.log("newVal: " newVal);
console.log("oldVal: " oldVal);
});
</script>
</pre>
<p>我的印象是 <code><Suspense></code> 需要包装 <code><PlayerRow></code> 组件,因为我正在使用 <code>storageRef</code> 和 < code>useStorageUrl</code> 方法,但它似乎引入了更多问题。根据 vuefire 文档并检查代码本身的定义,它们似乎并不是异步的,但是尝试立即调用它们不会产生立即/实际结果。</p>
<p><strong>相关软件包版本</strong></p>
<pre class="brush:json;toolbar:false;">{
"vue": "^3.2.45"
"firebase": "^9.15.0",
"typescript": "^4.9.3",
"vite": "^4.0.0",
"vue-router": "^4.1.6",
"vue-tsc": "^1.0.11",
"vuefire": "3.0.0-beta.6"
}
</pre></p>