Hyperlinking table data from API with vue-router - javascript

I'm taking in and displaying data from an API (https://restcountries.com/), but I'm having trouble hyperlinking each table entry to its own page with the router-link tag. Nothing happens, no errors, no linked text.
<template>
<div>
<b-table responsive head-variant="dark" striped hover :items="countries" :fields="headings">
<template #cell(name)="data">
<router-link :to="{name: 'country', params: {country: country.name.official}}">
{{ data.value }}
</router-link>
</template>
</b-table>
</div>
</template>
<script>
import axios from '#/config'
export default {
name: 'AllCountries',
components: {},
data() {
return {
headings: [
{
key: 'name.common',
sortable: true
},
{
key: 'capital',
sortable: true
},
],
countries: []
}
},
mounted() {
axios.get('/all')
.then(response => {
console.log(response.data)
this.countries = response.data
})
.catch(error => console.log(error))
}
}
</script>
If I remove the .common from the key 'name', router-link can work, but it will display all variations of country name rather than just the one 'common' name that I want. Also if .common is removed, the router-link does not work as it's shown here I get errors such as:
"TypeError: Cannot read properties of undefined (reading 'name')"
"Property or method "country" is not defined on the instance but referenced during render."
Though I don't get these errors with this exact router-link elsewhere, and 'name' wasn't defined in those files), the only way I've gotten the router-link to link is with these params: { id: data.item._id }(though it links to nothing (it tries to link to '/undefined?fullText=true'))

Params of the router-link should have been params: { country: data.item.name.official }}

Related

Vue 3 passing array warning: Extraneous non-props attributes were passed to component but could not be automatically inherited

please, I'm learning a VueJS 3 and I have probably begineer problem. I have warn in browser developer console like this one:
The Message is:
[Vue warn]: Extraneous non-props attributes (class) were passed to component but could not be automatically inherited because component renders fragment or text root nodes.
I'm passing array of objects to the child Component. In my parent views/Home.vue compoment I have this implemenation:
<template>
<div class="wrapper">
<section v-for="(item, index) in items" :key="index" class="box">
<ItemProperties class="infobox-item-properties" :info="item.properties" />
</section>
</div>
</template>
<script>
import { ref } from 'vue'
import { data } from '#/data.js'
import ItemProperties from '#/components/ItemProperties.vue'
export default {
components: {
ItemDescription,
},
setup() {
const items = ref(data)
return {
items,
}
},
</script>
In child compoment components/ItemProperties.vue I have this code:
<template>
<div class="infobox-item-property" v-for="(object, index) in info" :key="index">
<span class="infobox-item-title">{{ object.name }}:</span>
<span v-if="object.type === 'rating'">
<span v-for="(v, k) in object.value" :key="k">{{ object.icon }}</span>
</span>
<span v-else>
<span>{{ object.value }}</span>
</span>
</div>
</template>
<script>
export default {
props: {
info: {
type: Array,
required: false,
default: () => [
{
name: '',
value: '',
type: 'string',
icon: '',
},
],
},
},
}
</script>
It doesn't matter if I have default() function or not. Also doesn't matter if I have v-if condition or not. If I have cycle in the Array, I got this warning
Data are in data.js file. The part of file is here:
export const data = [
{
title: 'White shirt',
properties: [
{ name: 'Material', value: 'Cotton', type: 'string', icon: '' },
{ name: 'Size', value: 'M', type: 'string', icon: '' },
{ name: 'Count', value: 4, type: 'number', icon: '' },
{ name: 'Absorption', value: 4, type: 'rating', icon: '💧' },
{ name: 'Rating', value: 2, type: 'rating', icon: '⭐️' },
{ name: 'Confort', value: 2, type: 'rating', icon: '🛏' },
{ name: 'Sleeves', value: 'Short', type: 'string', icon: '' },
{ name: 'Color', value: 'White', type: 'string', icon: '' },
],
},
]
PS: Application works but I'm afraid about that warning. What can I do please like right way?
I will be glad for any advice. Thank you very much.
Well I think the error message is pretty clear.
Your ItemProperties.vue component is rendering fragments - because it is rendering multiple <div> elements using v-for. Which means there is no single root element.
At the same time, you are passing a class to the component with <ItemProperties class="infobox-item-properties" - class can be placed on HTML elements only. If you place it on Vue component, Vue tries to place it on the root element of the content the component is rendering. But because the content your component is rendering has no root element, Vue does not know where to put it...
To remove the warning either remove the class="infobox-item-properties" or wrap the content of ItemProperties to a single <div>.
The mechanism described above is called Fallthrough Attributes ("Non-prop attributes" Vue 2 docs). It is good to know that this automatic inheritance can be switched off which allows you to apply those attributes by yourself on the element (or component) you choose besides the root element. This can be very useful. Most notably when designing specialized wrappers around standard HTML elements (like input or button) or some library component...
The ItemProperties component has multiple root nodes because it renders a list in the root with v-for.
Based on the class name (infobox-item-properties), I think you want the class to be applied to a container element, so a simple solution is to just add that element (e.g., a div) in your component at the root:
// ItemProperties.vue
<template>
<div>
<section v-for="(item, index) in items" :key="index" class="box">
...
</section>
</div>
</template>
demo
You could also prevent passing down attributes in child components by doing this:
export default defineComponent({
name: "ChildComponentName",
inheritAttrs: false // This..
})
Source: https://vuejs.org/guide/components/attrs.html
This could also be triggered from parent components that have props: true in their route definition. Make sure that you add props: true only in the components that you actually need it and have some route params as props.
You are passing a class attribute to ItemProperties without declaring it.
Declare class in props options api should solve this issue.
ItemProperties.vue
...
export default {
props:["class"],
...
}

Access Router Params VueJS

I'm creating a blog using Vuejs and I'm fairly new to it.
In short. I have a dynamic list of elements that are loaded onto the screen and when you click on one of the items, I want to go to that page with the rest of the data. I followed somewhat of the same process I did if I was using React.
Router.js
export default new Router({
mode: "history",
base: process.env.BASE_URL,
routes: [
{
path: "/",
name: "Home",
component: Home
},
{
path: "/post/:id",
name: "PostView",
props: true,
component: PostView
}
]
});
I have my router setup to take dynamic links.
Component That Creates List Data
<template>
<router-link v-bind:to="{ path: 'post/' + post.postId, params: { post } }">
<div> .... </div>
</router-link>
</template>
<script>
import moment from "moment";
export default {
name: "recentPostTile",
props: {
post: Object,
},
};
</script>
As you can see that is how I did my router-link and in my actual Page View this is how I'm trying to read the data. 2 different way's I tried to read it.
props: {
post: Object
},
AND
data() {
return {
postData: this.$route.params.post, (also tried with $route)
};
},
and when I try to console out post/postData I just get undefined. I can assure you that the data does exist since the data is hard coded into the browser.
Also I tried to pass the params as key value pairs as well and that didn't work. I also create a new variable for the data I was going to be passing through the link. That didn't work either. I also haven't tired query, just because I don't want an ugly looking link.
Change your link to:
<router-link :to="{ name: 'PostView', params: { id: post.postId, postData: post } }">
<div> .... </div>
</router-link>
The changes:
Used the name property to specify the route
Removed the hardcoded path
Passed the id param, as that is the param name given in the route definition (/:id)

How to pass props to component in slot?

I am developing a Vue app with pimcore and twig in the backend. I have to create a component that receives the slot (another component), and render it inside, but with dynamic props.
Here is root in viani.twig.html:
<div>
<viani-accordion>
<viani-filter-checkbox v-slot="{filterRows}"></viani-filter-checkbox>
</viani-accordion>
</div>
There is nothing special. viani-accordion is a parent component and the viani-filter-checkbox is a slot, which I have to render with appropriate props.
Here you can see the VianiAccordion.vue:
<template>
<div class="wrapper">
<AccordionTitle v-for="(item, index) in dataToRender" :item="item" :key="index">
/*I think this is incorrect, but I'm trying to prop data that I need in viani-filter-checkbox*/
<slot :filter-rows="item.items"></slot>
</AccordionTitle>
</div>
</template>
<script>
import AccordionTitle from './Accordion-Title';
export default {
name: "Viani-Accordion",
components: {AccordionTitle},
data() {
return {
dataToRender: [
{
name: 'first item',
items: [
{
name: 'oil olive',
quantity: 10,
},
{
name: 'subitem 2',
quantity: 11,
},
]
},
{
name: 'second item',
items: [
{
name: 'subitem 1',
quantity: 10,
},
{
name: 'subitem 2',
quantity: 11,
},
]
}
]
}
},
}
</script>
Then I have another deeper child component Accordion-Title.vue that is responsible for rendering the slot (so I have to pass the slot through the multiple components):
<template>
<div v-if="isOpen" class="child-wrapper">
/*I think this is incorrect, but I'm trying to prop data that I need in viani-filter-checkbox*/
<slot :filterRows="item.items"></slot>
</div>
</template>
<script>
export default {
name: "Accordion-Title",
props: {
item: {
type: Object,
default: null
}
}
}
</script>
and finally Viani-FiltersCheckbox.vue:
<template>
<div>
//child component which we don't need in this case
<FilterCheckboxRow v-for="(item, index) in filterRows" :item="item" :key="index"/>
</div>
</template>
<script>
import FilterCheckboxRow from './FilterCheckboxRow'
export default {
name: "VianiFilterCheckbox",
components: {
FilterCheckboxRow
},
props: {
//here I expect to get array to render, but got default value (empty array)
filterRows: {
type: Array,
default: function () {
return []
}
},
},
}
</script>
So I need to pass the props (filterRows) to the component (Viani-FiltersCheckbox.vue), which is rendered as a slot. I have read this and this, but still don't get where the mistake and how to get the props I need.
It looks like you're trying to access your props through props.XXX. That's typically only done in templates for functional components. Otherwise, the props would be accessed without the props. prefix (i.e., props.item.items should be item.items).
And to pass filterRows from the scope data to the child component, declare a <template>, and then move your child into that, binding filterRows there:
<viani-accordion>
<!-- BEFORE: -->
<!-- <viani-filter-checkbox v-slot="{filterRows}"></viani-filter-checkbox> -->
<template v-slot="{filterRows}">
<viani-filter-checkbox :filterRows="filterRows"></viani-filter-checkbox>
</template>
</viani-accordion>

How to reset a component's props in VueJS upon reload?

I have a component in VueJS that displays a data table based off an array of objects. The component looks like this:
<template>
<div id="table">
<p class="title">Data:</p>
<v-data-table :headers="headers" :items="imported_data">
<template slot="items" slot-scope="props">
<td class="text-xs-left">{{props.item.name}}</td>
<td class="text-xs-right">{{props.item.age}}</td>
</template>
</v-data-table>
</div>
</template>
<script>
export default {
props: {
imported_data: {
type: Array,
required: true
}
},
data: () => ({
headers: [
{ text: "Name", value: "name" },
{ text: "Age", value: "age" }
]
})
};
</script>
I can render this component by doing <DataTableView :imported_data="this.dataset"/>, where this.dataset is an array of objects that populates my data table. My issue is when I go to reinitialize my component with another dataset, the new dataset just appends to the old dataset, it doesn't overwrite the old dataset with the new dataset. How can I make it so that my imported_data prop is reset when I put in new data?
Edit:
The data in imported_data is retrieved from the backend via a simple GET request:
axios
.get("http://localhost:8888/getData")
.then((res) => {
this.dataset = res.data
})
I have a button on the page that gets new data from my backend again, but the 'new' data is appending my 'old' data. The DataTableView component is loaded alongside the button, but isn't shown until this.imported_data is populated via a v-if conditional.

Passing dynamic data to Vue params/routes

I'm fairly new to Vue and am struggling to get something to work. Not entirely sure if this is possible but I'll ask and we'll see what the Stack Overflow gods have to conjure.
I wanted to know if it is possible to store component data/props for lots of IDs inside the data () portion of the default export.
So the {{$route.params.id}} manages to capture the id from the end of the url, but I want to know whether I can then have the View return other data stored somewhere in a component. So essentially is it possible for me to store data for let's say 5 different IDs all inside the Project.Vue file, or do I simply have to make 5 different files (Project1.Vue, Project2.Vue etc) and then set them all up as separate routes?
So far I have tried adding addings bits to the data () element such as
data () {
return {
msg: 'Projects',
id: [{ 1: 'hi'}, {2: 'hey'}],
two: 'project two',
three: 'project three',
}
}
And then referencing id inside the <template> but that didn't work as it simply returned the whole object. I also tried decoupling as mentioned here: https://router.vuejs.org/en/essentials/passing-props.html but had no joy with that either.
Apologies for my poor explanation but I hope somebody can help to shed light on whether this is possible. Code used below:
index.js
import Vue from 'vue'
import Router from 'vue-router'
import Home from '#/components/Home'
import Contact from '#/components/Contact'
import About from '#/components/About'
import Projects from '#/components/Projects'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/contact',
name: 'Contact',
component: Contact
},
{
path: '/about',
name: 'About',
component: About
},
{
path: '/projects',
name: 'Projects',
component: Projects
},
{
path: '/projects/:id',
name: 'Projects',
component: Projects,
props: {default: true}
},
]
})
Projects.Vue
<template>
<div id="projects">
<h1>{{ header }} {{ $route.params.id }}</h1>
{{id}}
</div>
</template>
<script>
export default {
name: 'Projects',
watch: {
'$route'(to, from) {
// react to route changes...
}
},
data () {
return {
header: 'Projects',
}
}
}
</script>
<style scoped>
</style>
I have managed to figure it out.
In order to dynamically pass data based on the id passed in to the url, you need to create a data object and then inside of the of the <template>, you can pass in the object you have created but then pass the $route.params.id inside of the square brackets. However, it's worth noting that because the object created inside of your data() will use the zero index, it is worth adding a -1 inside of the template. See the below code to understand how it all works:
<template>
<div id="projects">
<h1>{{ msg }} {{ projects[$route.params.id - 1] }}</h1>
</div>
</template>
<script>
export default {
name: 'Projects',
watch: {
'$route'(to, from) {
// react to route changes...
}
},
data () {
return {
projects: [
{ id: 1,
name: 'Project numero uno'
},
{ id: 2,
name: 'Project secundo'
},
{ id: 3,
name: 'Project three'
},
]
}
}
}
</script>
<style scoped>
</style>

Categories