Vuejs: pass in an array of hashes to prop - vue.js

This is probably a trivial question but I haven't figured out how to get this working. I am trying to pass an array of hashes as a prop to another component in my Vue.js project.
My code looks something like this:
Sidebar.vue
<template>
<b-nav vertical>
<b-nav-item v-for='nav in items' :key='nav.title' :to='nav.url'>
{{nav.title}}
</b-nav-item>
</b-nav>
</template>
<script>
export default {
props: {
items: {
type: Array,
default: () => []
}
}
}
</script>
And I am calling this code as such:
<template>
...
<sidebar :items="myItems"/>
...
</template>
<script>
import Sidebar from '~/components/Sidebar.vue'
export default {
components: {
Sidebar
},
data: () => {
return {
myItems: [
{
title: 'Link 1',
url: '/link1'
},
{
title: 'Link 2',
url: '/link2'
},
{
title: 'Link 3',
url: '/link4'
},
{
title: 'Link 4',
url: '/link4'
}
]
}
}
}
</script>
However, nothing is appearing on the page.
How can I pass the array of hashes as a prop to my Sidebar component?
Thanks in advance!

You should change the arrow function data: () => { to data() {
According to the documentation:
Note that you should not use an arrow function with the data property (e.g. data: () => { return { a: this.myProp }}). The reason is arrow functions bind the parent context, so this will not be the Vue instance as you expect and this.myProp will be undefined.

Related

how can I mock a component when I make a storyBook?

I'm making a storyBook out of a component that we'll call ParentComponent, it has a child called ChildComponent.
Inside ChildComponent there is a function that makes a call to the code of another repository so this is where the storybook fails.
components: {
DataProvider: () => import("common-data-provider"),// --> problem this line
}
How can I mock ChildComponent and not make this request?
StoryBook of Parent Component
import articlesFunds from "../__fixtures__/articles.json";
export default {
component: ParentComponent,
argTypes: {
getConfigFilesDataProvider: {
control: "object",
},
},
args: {
getConfigFilesDataProvider: () => new Promise((resolve) => setTimeout(() => resolve({ data: articlesFunds }))),
},
};
export const Default = (args) => ({
components: { ParentComopnent },
props: Object.keys(args),
template: `
<ParentComopnent
:data-provider="getConfigFilesDataProvider"
/>
`,
});
ChildComponent
<template>
<DataProvider :data="articles" />
</template>
<script>
export default {
name: "ChildComponent",
components: {
DataProvider: () => import("common-data-provider"),// --> problem this line
ComponentB
},
props: {
articles: {
type: Array,
required: true,
},
}
};
</script>
Storybook doesn't specifically provide the support for module mocking, but it's possible to use Webpack aliases for that.
Dependency injection of any kind can be used to modify the behaviour of a component in uncommon conditions, e.g. the ability to provide custom component:
props: {
articles: {
type: Array,
required: true,
},
dataProvider: {
type: null,
default: () => import("common-data-provider"),
}
}
And used like dynamic component:
<component :is="dataProvider" :data="articles"/>
Then custom implementation can be provided through dataProvider prop.

How make json data available for my Vue dynamic routes

I have a List component where I fetch my date from db/blogs.json:
created() {
fetch('http://localhost:3000/blogs')
.then(response => {
return response.json();
})
.then(data => {
this.blogs = data;
})
},
In my BlogDetail.vue I have:
<script>
export default {
data: () => {
return {
blogId:this.$route.params.id
}
},
computed: {
blog() {
return this.blogs.find(
blog => blog.id === this.blogId
)
}
}
}
</script>
But how do I get the blogs data in this component, which I fetched in the List component?
Because now in the <template> section of my BlogDetail.vue I cannot access e.g. {{ blog.name }}
Update:
I try passing blogs with props:
Now I am accepting a prop in BlogDetails.vue:
props: {
blogs: {
type: Array
}
},
But from where (which component), I have to registering the prop like :blogs="blogs"?
Update 2:
This is what I have so far, link to the sandbox
Here is the working sandbox.
Firstly you need to import JSON data from your JSON file correctly. As:
<script>
import ListItem from "./ListItem";
import Blogs from "../../public/db/blogs.json";
export default {
name: "List",
components: {
ListItem
},
data() {
return {
blogs: Blogs.experiences
};
},
created() {}
};
</script>
Have to send props in the router-link as :
<router-link
:to="{ name: 'BlogDetails', params: { id: blog.id,blog:blog }}">More information
</router-link>
You can send props to the child component in the tag name, in your case:
//LIST component(PARENT)
<tamplate>
<BlogDetail :blogs="blogs"></BlogDetail> //CHILD component
</template>

How to make this vue component reusable with it's props?

I am trying to make this component reusable so later can install it in any project and via props add needed values e.g. images and function parameters (next, prev, intervals...) inside any component.
<template>
<div>
<transition-group name='fade' tag='div'>
<div v-for="i in [currentIndex]" :key='i'>
<img :src="currentImg" />
</div>
</transition-group>
<a class="prev" #click="prev" href='#'>❮</a>
<a class="next" #click="next" href='#'>❯</a>
</div>
</template>
<script>
export default {
name: 'Slider',
data() {
return {
images: [
'https://cdn.pixabay.com/photo/2015/12/12/15/24/amsterdam-1089646_1280.jpg',
'https://cdn.pixabay.com/photo/2016/02/17/23/03/usa-1206240_1280.jpg',
'https://cdn.pixabay.com/photo/2015/05/15/14/27/eiffel-tower-768501_1280.jpg',
'https://cdn.pixabay.com/photo/2016/12/04/19/30/berlin-cathedral-1882397_1280.jpg'
],
timer: null,
currentIndex: 0,
}
},
mounted: function() {
this.startSlide();
},
methods: {
startSlide: function() {
this.timer = setInterval(this.next, 4000);
},
next: function() {
this.currentIndex += 1
},
prev: function() {
this.currentIndex -= 1
}
},
computed: {
currentImg: function() {
return this.images[Math.abs(this.currentIndex) % this.images.length];
}
}
}
</script>
styles...
So later it would be <Slider... all props, images loop here/> inside other components.
How can be it be achieved?
Just move what needs to come from another component to props. That way other component can pass the relevant info it needs.
export default {
name: 'Slider',
props: {
images: Array,
next: Function
prev: Function,
// and so on
},
...
The parent component would call it like:
<Slider :images="imageArray" :next="nextFunc" :prev="prevFunc" />
EDIT
You can pass an interval value via props:
export default {
name: 'Slider',
props: { intervalVal: Number },
methods: {
startSlide: function() {
this.timer = setInterval(this.next, this.intervalVal);
},
}
You can also pass function from parent to child via props.
export default {
name: 'Slider',
props: { next: Function },
methods: {
someMethod: function() {
this.next() // function from the parent
},
}
I don't really understand your use case 100% but these are possible options.

How to test Vue components that need to react to child-sourced events?

I’m having a hard time figuring out how to test single file Vue components that need to react to child-sourced events.
I have a Gallery component, which has any number of Card components as children, and must keep track of the Cards that are selected. When a card is clicked, it emits a custom event (card-click) that the Gallery listens for and reacts to by calling a select() function. Select() has some logic based on whether or not the Shift or Meta keys are pressed. I’ve created simplified versions of these components below:
Gallery Component
<template>
<div class="gallery">
<card v-for="(item, index) in items"
:id="item.id"
:key="item.id"
class="galleryCard"
#card-click="select(item.id, $event)">
<h2>{{ item.title }}</h2>
</card>
</div>
</template>
<script>
export default {
name: "Gallery",
props: {
items: {
required: true,
type: Array,
},
},
methods: {
select: function(id, event) {
if(event.metaKey) {
console.log('meta + click')
} else {
if (event.shiftKey) {
console.log('shift + click')
} else {
console.log('click')
}
}
},
},
}
</script>
Card Component
<template>
<article :id="id" #click="handleSelect($event)" class="card">
<slot/>
</article>
</template>
<script>
export default {
name: "Card",
props: {
id: {
type: String,
default: "",
},
},
methods: {
handleSelect: function(event) {
this.$emit("card-click", event)
},
},
}
</script>
Using Jest, I’m trying to test the Gallery component’s select() function, which I’m mocking (selectStub), to make sure the correct Cards are selected when clicked. When I trigger clicks on two of the cards, I expect to see something in the selectStub.mock.calls array, but there is nothing. I have also tried to emit the event How can I capture events that children emit in my test?
Gallery Test
import { createLocalVue, mount } from "#vue/test-utils"
import Gallery from "#/Gallery.vue"
import Card from "#/Card.vue"
const localVue = createLocalVue()
let wrapper
let items = [
{ id: "1", title: "First" },
{ id: "2", title: "Second" },
{ id: "3", title: "Third" },
]
describe("Gallery.vue", () => {
beforeEach(() => {
wrapper = mount(Gallery, {
localVue,
propsData: {
items: items,
},
stubs: ["card"],
})
})
const selectStub = jest.fn();
it("selects the correct items", () => {
wrapper.setMethods({ select: selectStub })
const card1 = wrapper.findAll(".galleryCard").at(0)
const card2 = wrapper.findAll(".galleryCard").at(1)
card1.trigger('click')
card2.trigger('click', {
shiftKey: true
})
console.log(selectStub.mock)
})
})
All properties (calls, instances, and timestamps) of selectStub.mock are empty. I have also tried emitting the card-click event via vue-test-utils wrapper.vm.$emit() function but it doesn't trigger the select.
How can I test the Gallery select() method to make sure it's responding correctly to a child-sourced event?

Nuxt.js / Vuex - using Namespace modules in mapActions helper, [vuex] unknown action type: FETCH_LABEL

I am trying to map an action to a component using mapActions helper from vuex. Here is my labels.js vuex module:
export const FETCH_LABELS = 'FETCH_LABELS'
export const FETCH_LABEL = 'FETCH_LABEL'
const state = () => ({
labels: [
{ name: 'Mord Records', slug: 'mord', image: '/images/labels/mord.jpg'},
{ name: 'Subsist Records', slug: 'subsist', image: '/images/labels/subsist.jpg'},
{ name: 'Drumcode Records', slug: 'drumcode', image: '/images/labels/drumcode.png'},
],
label: {} // null
})
const mutations = {
FETCH_LABEL: (state, { label }) => {
state.label = label
},
}
const actions = {
fetchLabel({commit}, slug) {
let label = state.labels.filter((slug, index) => {
return slug == state.labels[index]
})
commit(FETCH_LABEL, { label })
},
}
const getters = {
labels: state => {
return state.labels
},
label: (state, slug) => {
}
}
export default {
state,
mutations,
actions,
getters
}
Here is my component _slug.vue page where I want to map the fetchLabel action:
<template>
<div class="container">
<div class="section">
<div class="box">
<h1>{{ $route.params.slug }}</h1>
</div>
</div>
</div>
</template>
<script>
import { mapGetters, mapActions } from "vuex";
export default {
data() {
return {
title: this.$route.params.slug
};
},
computed: {
// Research
// labels() {
// return this.$store
// }
...mapGetters({
labels: "modules/labels/labels"
})
},
components: {},
methods: {
...mapActions({
fetchLabel: 'FETCH_LABEL' // map `this.add()` to `this.$store.dispatch('increment')`
})
},
created() {
console.log('created')
this.fetchLabel(this.$route.params.slug)
},
head() {
return {
title: this.title
}
},
layout: "app",
}
</script>
<style>
</style>
However inside the created() lifecycle hook at this.fetchLabel(this.$route.params.slug) it throws the following error in the console:
[vuex] unknown action type: FETCH_LABEL
What am I missing or doing wrong? Please help me solve this.
Note that in Nuxt.js:
Modules: every .js file inside the store directory is transformed as a namespaced module (index being the root module).
You are using:
Here is my labels.js vuex module:
with labels.js as you stated above so you'll need to access everything as namespaced modules so your mapAction helper should be like as such:
methods: {
...mapActions({
nameOfMethod: 'namespace/actionName'
})
}
So you would have this:
...mapActions({
fetchLabel: 'labels/fetchLabel'
})
You could also clean it up by doing so for when you'd like to retain the name of your action as your method name.
...mapActions('namespace', ['actionName']),
...
So you would have this:
...mapActions('labels', ['fetchLabel']),
...
In both cases the computed prop should work without a problem.
Your action name is fetchLabel and not FETCH_LABEL (which is a mutation). In mapActions change to
methods: {
...mapActions({
fetchLabel
})
},