Could someone explain why useQuery is throwing an error? Both ways works, but way 1 is throwing an error to console. I want to use useQuery because of the refetch method. Im using vue 3 with apollo.
<div class="ml-2">
<table class="w-[100%]">
<tr>
<th class="w-1/6">Name</th>
<th class="w-1/6">Level</th>
<th class="w-1/6">Inhaber</th>
<th class="w-1/6">Type</th>
<th class="w-1/6">Abholbereit</th>
<th class="w-1/6">Aktionen</th>
</tr>
<AppCommunityBuilding
v-for="building in result.getBuildings"
:building="building"
#item-collected="refetch"
/>
{{ result.getBuildings }}
</table>
</div>
</template>
<script setup>
import apolloClient from '#/plugins/apollo';
import { useQuery } from '#vue/apollo-composable';
import { getBuildings } from '../../../apollo/queries';
import AppCommunityBuilding from './AppBuilding.vue';
/**
* Way 1
* Using useQuery for the refetch method results in an error:
* TypeError: Cannot read properties of undefined (reading 'getBuildings')
*/
const { result, loading, refetch } = useQuery(getBuildings);
/**
* Way 2
* Using the apolloClient results in no error
*/
const { data } = await apolloClient.query({ query: getBuildings }) // No error
</script>
Related
I am trying to populate a table of people with their name and a profile picture. The name is sourced from a Firestore database, and the picture uses the name to find a related picture in a Firebase Storage bucket.
I have watched hours of videos and have scoured nearly a hundred articles at this point, so my example code has pieces from each as I've been trying every combination and getting mixed but unsuccessful results.
In this current state which returns the least amount of errors, I am able to successfully populate the table with the names, however in that same component it is not able to pull the profile picture. The value used for the profile picture is updated, but it is updated from the placeholder value to undefined.
GamePlanner.vue
<template>
<div>
<Field />
<Bench />
<!-- <Suspense>
<template #default> -->
<PlanSummary />
<!-- </template>
<template #fallback>
<div class="loading">Loading...</div>
</template>
</Suspense> -->
</div>
</template>
PlanSummary.vue
<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>
PlayerRow.vue
<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>
I was under the impression that <Suspense> would need to wrap the <PlayerRow> component since I am using the storageRef and useStorageUrl methods, but it seems to introduce more issues. Based on the vuefire documentation and inspecting the definitions int he code itself, it does not appear that they are asynchronous, however trying to to immediately invoke them does not produce an immediate/actual result.
Relevant Package Versions
{
"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"
}
According to this documentation we are supposed to use useCollection with including collection as a argument as follows:
const todos = useCollection(collection(db, `/gameplans/${gamePlanID}/participants`))
And I see you are using it the correct way but instead of assigning to a ref you can assign it directly to players variable. As you are trying to use this reactive object as the value of a ref object, which is not supported.
You can solve your issue with changing this line:
const players = ref();
players.value = useCollection(collection(db, `/gameplans/${gamePlanID}/participants`));
To :
const players = useCollection(collection(db, `/gameplans/${gamePlanID}/participants`));
For more information about this topic you can go through the following docs
I am learning Vue JS and I am getting data from the backend using Axios to show it in data table. When I get the data and do a v-for I get No data available in table Below is my code.
Picture
Template
<tbody >
<tr v-for="user in users" v-bind:key="user">
<td>{{user.firstname}} {{user.lastname}}</td>
<td>{{user.username}}</td>
<td>{{user.role}}</td>
<td>{{user.email}}</td>
<td>{{user.is_active}}</td>
<td>View</td>
</tr>
</tbody>
And script
import axios from 'axios';
export default {
name : "SystemAdmin",
data(){
return{
users: []
}
},
mounted(){
this.checkUser()
},
methods:{
async checkUser(){
try {
console.log('System Users')
const response = await axios.get('superadmin/system-admins/');
this.users = response.data.results
console.log(response.data.results)
} catch (err) {
}
}
}
}
Did I miss something?
I'm reading this article: https://v3.vuejs.org/guide/component-dynamic-async.html#using-with-suspense
It's referring a concept called "suspensible" component.
I have researched, but I can't find any information about what is a so called "suspensible" component.
Can anyone explain what it is? Thanks!
"Suspensible" means replaceable by fallback content while parent <Suspense> resolves async child components found in its <template #default>.
The concept is borrowed from React's Suspense API.
In more detail, <Suspense> is a built-in Vue 3 component which renders a <template #fallback> instead of the <template #default>, until all async child components in default template are resolved.
In order to be suspensible, a component's rendering needs to depend on a promise:
be loaded using () => import('some/path')
or use an async/await (or any other form of Promise syntax) in its setup function
A suspensible component is suspensed when included in a <Suspense>'s default template, while its parent <Suspense> has not resolved all its suspensible components, even if the suspensed component itself has already resolved.
Obviously, <Suspense> components themselves are suspensible and suspensing can be nested.
Here's a more detailed explanation on <Suspense> in Vue 3.
Among other usages, <Suspence> provides an elegant and intuitive way to resolve the common problem of having to wrap child components and templates in v-if guarding against non-existent properties on data which has not yet been loaded.
A typical Vue 2 example:
Vue.config.devtools = false;
Vue.config.productionTip = false;
Vue.component('render-items', {
props: ['items'],
template: `<table>
<tr>
<th>Id</th>
<th>User Id</th>
<th>Title</th>
</tr>
<tr v-for="(item, key) in items" :key="key">
<td v-text="item.id"></td>
<td v-text="item.userId"></td>
<td v-text="item.title"></td>
</tr>
</table>`
});
new Vue({
el: '#app',
data: () => ({
items: []
}),
computed: {
hasData() {
return this.items.length;
}
},
async created() {
const items = await fetch('https://jsonplaceholder.typicode.com/posts')
.then(r => r.json());
this.items = items;
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.12/vue.js"></script>
<div id="app">
<render-items :items="items" v-if="hasData"></render-items>
<template v-else>loading...</template>
</div>
Same example (more or less) in Vue 3, using <Suspense> and async setup:
const RenderItems = Vue.defineComponent({
async setup() {
const items = await fetch('https://jsonplaceholder.typicode.com/posts')
.then(r => r.json());
return Vue.reactive({ items });
},
template: `<table>
<tr>
<th>Id</th>
<th>User Id</th>
<th>Title</th>
</tr>
<tr v-for="(item, key) in items" :key="key">
<td v-text="item.id"></td>
<td v-text="item.userId"></td>
<td v-text="item.title"></td>
</tr>
</table>`
});
const App = { components: { RenderItems }};
Vue.createApp(App).mount('#app');
<script src="https://unpkg.com/vue#next/dist/vue.global.prod.js"></script>
<div id="app">
<Suspense>
<template #default>
<render-items></render-items>
</template>
<template #fallback>
loading...
</template>
</Suspense>
</div>
One major advantage is in the Vue 3 example we can contain the data fetcher (and the data) in the child component. This is not possible in Vue 2, because:
the sub-component is only created after data has loaded
the parent needs to know when the condition changed (so it needs access to the actual condition) in order to switch between rendering fallback content or the child component.
The simplest way to do it in Vue 2 is actually to load the data in parent and pass the result to the child component, via props. If you have a lot of sub-components, this pattern can get messy.
In Vue 3, the responsibility for loading the data and checking the condition can rest entirely with the child-component. Parent doesn't need access to the actual condition.
Just like <template>, <Suspense> does not create a DOM element.
In the above Vue 3 example, <RenderItems /> is suspensible.
A suspensible component would be one that is capable of using the new item in Vue 3. A suspense item it something that loads and may take a longer time to load, like an API call. Generally you would be using async/await inside of items that are inside of the suspense item.
A lot of good info here: https://v3.vuejs.org/guide/component-dynamic-async.html#async-components
You would use a suspense item to say while items inside of the suspense item are being awaited show something else (like a skeleton loader).
I have 2 files one called clients.js with data and one called clients.vue and I would like to import the data and list it out in a table in the clients.vue file; but I can not access it when importing.
It's working fine if I move the array in to the clients.vue-file (so without import).
My code looks (simplified) like this:
Clients.vue
<template>
<div>
<table>
<tbody>
<tr v-for="client in clients">
<td>{{ client.ClientName }}</td>
<td>{{ client.ClientId }}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import clients from "./data/clients.js";
export default {};
</script>
And my clients.js file:
export default {
data () {
return {
clients: [
{
ClientName: 'testname1',
ClientId: 1
},
{
ClientName: 'testname2',
ClientId: 2
}
]
}
}
}
I know probably have "activate" the imported array somehow, but i do not know how.
You would have to expose a property defined in your component's data object.
You have imported the clients but not injected it into your data of Clients.vue.
Change the Clients.vue like so.
<script>
import clients from "./data/clients.js";
export default {
...clients
};
This will inject the clients into Client.vue. Then you can use that in your template of Client.vue.
Alternative:
However this is not a good pattern in terms of readability.
It would be better to have a property in the component itself and then set the property in mounted hook.
You can do so in the following way:
<template>
<div>
<table>
<tbody>
<!-- use key when iterating -->
<tr v-for="client in clients" :key="client.ClientId">
<td>{{ client.ClientName }}</td>
<td>{{ client.ClientId }}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import clients from "./data/clients.js";
export default {
clients: [] // initial value
},
mounted () { // setting clients in mounted
this.clients = { ...clients } // use Vue.set if this is a deep nested object
},
</script>
<style lang="scss" scoped></style>
Mixin way
If you intend to use clients.js as an mixin.
Then change your script to this:
<script>
import clients from "./data/clients.js";
export default {
mixins: [clients],
}
</script>
I am having problems displaying information from an database to the angular 2 *ngFor loop. This is my code so far:
.Net Core controller:
// GET: api/Hats
[HttpGet("GetHats")]
public IEnumerable<Hat> GetHats()
{
return _context.Hat;
}
hat-list.component.ts:
//Imports from #Angular
import { Component, OnInit } from '#angular/core';
import { Http } from '#angular/http';
//Other imports
import { HatListService } from '../service/service.component';
import Hat = App.Models.IHat;
//Component Config
#Component({
selector: 'hat-list',
templateUrl: './hat-list.component.html',
providers: [HatListService]
})
//Class
export class HatListComponent implements OnInit {
public Hats: any[];
constructor(public _hatListService: HatListService) {
}
//OnInit
ngOnInit() {
this.getAllHats();
}
//Get All
getAllHats() {
//debugger
this._hatListService.getHatList().subscribe(foundHats =>
this.Hats = foundHats
);
}
}
service.component.ts
//imports from Angular
import { Injectable, Component } from '#angular/core';
import { Http, Request, RequestMethod, Response, RequestOptions, Headers } from '#angular/http';
import 'rxjs/Rx';
import { Observable } from 'rxjs/Observable';
//Other imports
import Hat = App.Models.IHat;
#Component({
providers: [Http]
})
//Exported class
#Injectable()
export class HatListService {
public headers: Headers;
constructor(private _http: Http) {
}
public _getUrl: string = '/api/Hats/GetHats';
//Get
getHatList(): Observable<Hat[]> {
//debugger
return this._http.get(this._getUrl).map(res => <Hat[]>res.json())
.catch(this.handleError);
}
private handleError(error: Response) {
return Observable.throw(error.json().error || 'Opps!! Server error');
}
}
hat-list.component.html:
<table class="table table-hover table-striped table-responsive">
<thead>
<tr>
<th>Name</th>
<th>Color</th>
<th>Count</th>
<th></th>
</tr>
</thead>
<tbody>
<tr *ngFor="let hat of Hats"">
<td>{{hat.Name}}</td>
<td>{{hat.Color}}</td>
<td>{{hat.Count}}</td>
<td>
<a class="glyphicon glyphicon-remove" (click)="delete(hat)"></a>
</td>
</tr>
</tbody>
</table>
Picture of the table
The *ngFor recives the value.
I know the request to the database happens asynch and that one problem might be that the *ngfor loop fires before the data has returned and thus no information is displayed. But diffrent articles on the net say that the *ngFor loop shouldt fire unless the iterable has a value. The strange thing is that when I update the database manually thru SSMS the *ngfor recognises the added content add creates additioanl rows. What am I doing wrong.
Thanx for all the help!
There are wrong variable names used in the template i.e. {{hat.Name}} instead of {{hat.name}}.