v-for loop not working with imported array - vue.js

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>

Related

Unable to fetch data using the Flickr API

I'm trying to build a simple photo gallery web application using Vue JS as a way to learn Vue JS. I am attempting to use the Flickr API (https://api.flickr.com/services/feeds/photos_public.gne?format=json) in my web app and I'm trying to fetch data from the above URL but unable to. The following is my code. It's still work in progress hence why its missing a lot things and I just want to see the response in the console.
<template>
<div class="container">
<div>
<h1>TEST</h1>
<tbody>
<td v-for="(image, index) in images" :key="index">
{{ image }}
</td>
</tbody>
</div>
</div>
</template>
<script>
import axios from "axios";
export default {
name: "ImageFeed",
data() {
return {
images: [],
};
},
methods: {},
computed: {},
mounted() {
axios
.get(
"https://api.flickr.com/services/feeds/photos_public.gne?format=json"
)
.then((response) => {
console.log(response);
});
},
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped></style>
I get the following console error:
however when I test in Postman, I get the following response, which is the end goal:
I would appreciate any help!

What is a suspensible component in Vue 3?

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

Vue passing props to route components

I'm trying to pass data from the Vue instance through to components through router. I've tried to following along the docs and examples in stack overflow but can't get it to work.. I'm trying to pass a prop called "funds", below is my scripts and components:
main.js
new Vue({
el: '#app',
router,
components: {
App
},
data: {
funds: [...]
},
template: '<App :funds="funds"/>'
});
App.vue
<template>
<div id="app">
...
<router-view :funds="funds" />
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
Vue.use(Router)
export default new Router({
routes: [
...
{
path: '/funds',
name: 'funds',
component: List
}
]
})
List.vue
<template>
<div>
<table class="table table-striped">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
</tr>
</thead>
<tbody>
<tr v-for="fund in funds" :key="fund.name">
...
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
name: 'List',
props: ['funds']
}
</script>
I am seeing a warning in the console:
[Vue warn]: Property or method "funds" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.
found in
---> <App> at src/App.vue
<Root>
However, when I run this I don't see any rows in the table being rendered, in fact the table is just empty (only header). Am I missing something in the chain?
In App.vue, You need to add props too as shown below;
<template>
<div id="app">
...
<router-view :funds="funds" />
</div>
</template>
<script>
export default {
name: 'App',
props: ['funds']
}
</script>
Vue.use(Router)
export default new Router({
routes: [
...
{
path: '/funds',
name: 'funds',
component: List
}
]
})

Inject component tag dynamically

I have multiple drop divs and so every time i drop the image of a component into those div i import the component corresponding to this image and i need to put it into the dom in the target div
drops[i].addEventListener('drop', function () {
if (this.draggedElement === null) {
return false
}
// get the component conf of this image
let conf = BlocksStore.conf[this.draggedElement.dataset.category].blocks[this.draggedElement.dataset.type]
// get the component itself (./blocks/SimpleTitle.vue)
let component = require('./blocks/' + conf.component)
drops[i].classList.remove('drag-enter')
// here is where i don't know what to do ...
drops[i].innerHTML = component
this.draggedElement = null
}.bind(this))
Here the code of ./blocks/SimpleTitle.vue
<template>
<tr>
<td align="center">
Lorem Ipsum
</td>
</tr>
</template>
<script>
export default {
name: 'simple-title'
}
</script>
<style>
</style>
I already tried to append the tag instead of the component but the dom don't comprehend it as a component

How to create a custom link component in Vue.js?

This seems like your run-of-the-mill master/detail use case, but the examples in the Vue docs stop short of examples for this. I have a Mail Folder page (route /:mailbox_id) that displays a table of emails by Date, Subject, etc., and I want a nested route (/:message_id) that shows the email's text when the user clicks on a row.
I was able to do this in Ember (recreating this) because Ember makes a JavaScript onClick function to handle the routing and lets you set the HTML element to render, and then you just pass whatever objects to the child route.
But I'm new to Vue.js, and I've been going through the docs but can't grok how to accomplish the same thing. I can't figure out how to either create a custom link component, or how to use the built-in Vue <router-link>component (because I need it to be a <tr> instead of <a>) to both go to the child route, and pass along the contents of the message to it so it can be shown.
If it helps, here's some code:
The Router
export default new Router({
routes: [
{
path: '/:id',
name: 'mailbox',
component: Mailbox,
props: true,
children: [
{
path: 'mail/:id',
name: 'mail',
component: Mail,
props: true
}
]
}
]
})
Component: Mailbox.vue
<template>
<div>
<table>
<tr>
<th>Date</th>
<th>Subject</th>
<th>From</th>
<th>To</th>
</tr>
<Mail-List-Item v-for="message in messages" :key="message.id" v-bind:message="message"/>
</table>
<router-view></router-view>
</div>
</template>
<script>
import MailListItem from './Mail-List-Item'
export default {
components: { 'Mail-List-Item': MailListItem },
name: 'Mailbox',
props: ['messages']
}
</script>
Component: Mail.vue
<template>
<div class="mail">
<dl>
<dt>From</dt>
<dd>{{mail.from}}</dd>
<dt>To</dt>
<dd>{{mail.to}}</dd>
<dt>Date</dt>
<dd>{{messageDate}}</dd>
</dl>
<h4>{{mail.subject}}</h4>
<p>{{mail.body}}</p>
</div>
</template>
<script>
export default {
props: ['message', 'messageDate']
}
</script>
Component: Mail-List-Item.vue
<template>
<V-Row-Link href="mail" mailid="message.id" message="message">
<td>{{messageDate}}</td>
<td>{{message.subject}}</td>
<td>{{message.from}}</td>
<td>{{message.to}}</td>
</V-Row-Link>
</template>
<script>
var moment = require('moment')
import VRowLink from './V-Row-Link'
export default {
name: 'Mail-List-Item',
props: ['message'],
components: { VRowLink },
data: function () {
return {messageDate: moment(this.message.date).format('MMM Do')}
}
}
</script>
Component: V-Row-Link.vue (much of this copied from this repo)
<template lang="html">
<tr
v-bind:href="href"
v-on:click="go"
>
<slot></slot>
</tr>
</template>
<script>
import routes from '../Router'
export default {
props: ['href', 'mailid', 'message'],
methods: {
go (event) {
this.$root.currentRoute = this.href
window.history.pushState(
null,
routes[this.href],
this.href
)
}
}
}
</script>
Router link takes a tag attribute, that you can use to turn it into any element you like. An example would be...
<router-link tag="tr" :to="'/messages/' + MAIL_ID">{{ MAIL_TITLE }}</router-link>