I'm new to vue.js and I'm tryeing to build a little application, where I in one case need to pass a prop between two components. For some reason it does not work and I don't know why.
Here is the first component, the Playlist.Vue component:
<template>
<div class="playlists-con">
<div class="playlists">
<h1>Available Playlists for category: {{id}}</h1>
<ul>
<router-link v-for="playlist in playlists" :to="`details/${playlist.id}`" tag="li" active-class="active" exact>
<div class="playlist-wrapper">
<div class="imgWrap">
<img :src="playlist.images[0].url" />
</div>
<a>{{playlist.name}}</a>
</div>
</router-link>
</ul>
</div>
<div>
<router-view category="id"></router-view>
</div>
</div>
</template>
<script>
export default {
data() {
return {
id: this.$route.params.id,
playlists : []
}
},
watch: {
'$route'(to, from) {
this.id = to.params.id
}
},
methods: {
fetchPlaylist() {
this.$http.get('' + this.id + '/playlists')
.then(response => {
return response.json()
})
.then(data => {
const playlist_items = data.playlists.items;
for (let key in playlist_items) {
this.playlists.push(playlist_items[key])
}
})
}
},
created() {
this.fetchPlaylist();
}
}
</script>
from the Playlist component, I'm supposed to be able to get to the Playlist details. I also want to pass the category prop to the PlaylistDetails.vue, so I tried to do <router-view category="id"></router-view> - but that does not work.
PlaylistDetails.vue component (where I want to display the category prop, passed from the Playlist.vue component) :
<template>
<div class="playlist-details">
<router-link :to="`/categories/${category}`">Go to playlists</router-link>
<h1>Playlist Details for Playlist: <span class="playlist-name">{{playlistName}}</span></h1>
<h1>category: {{ category }}</h1>
<ul>
<li v-for="track in tracks">
<p>{{ track.track.artists[0].name}} - {{ track.track.name }}</p>
</li>
</ul>
</div>
</template>
<script>
export default {
props: ['category'],
data() {
return {
id: this.$route.params.id,
tracks : [],
playlistName: ''
}
},
watch: {
'$route'(to, from) {
this.path = from.params.path
}
},
beforeRouteEnter(to, from, next) {
if (true) {
next();
} else {
next(false);
}
},
methods: {
fetchPlaylistDetails() {
this.$http.get('https://api.spotify.com/v1/users/spotify/playlists/' + this.id)
.then(response => {
return response.json()
})
.then(data => {
const playlist_tracks = data.tracks.items;
for (let key in playlist_tracks) {
this.tracks.push(playlist_tracks[key])
}
this.playlistName = data.name;
})
}
},
created() {
this.fetchPlaylistDetails();
}
}
</script>
What am I doing wrong?
UPDATE
Here is my router configuration:
export const routes = [
{
path: '', default: App
},
{
path: '/categories/:id/playlists', props: true, component: Playlists
},
{
path: '/categories/:id/details/:id', component: PlaylistDetails, props: true, beforeEnter: (to, from, next) => {
next();
}},
{path: '*', redirect: '/'}
]
You are half way there, you defined props:true on the route, which means every dynamic property that is matched in the url would be passed as a prop, so :
//this will pass 'id' as a prop to the playlist component
{
path: '/categories/:id/playlists', props: true, component: Playlists
},
So inside the playlist component you'll have this:
props: ['id'],
data() {
return {
playlists : []
}
},
The same is true for the details component:
//change the name of the param to distinguish it from the category id
{
path: '/categories/:id/details/:detailsId', component: PlaylistDetails, props: true, beforeEnter: (to, from, next) => {
next();
}},
And in PlaylistDetails.vue:
props: ['detailsId'],
....
methods: {
fetchPlaylistDetails() {
this.$http.get('https://api.spotify.com/v1/users/spotify/playlists/' + this.detailsId)
.then(response => {
return response.json()
})
.then(data => {
const playlist_tracks = data.tracks.items;
for (let key in playlist_tracks) {
this.tracks.push(playlist_tracks[key])
}
this.playlistName = data.name;
})
}
},
There're 2 ways to pass data between non-parent components. I would recommend to take a look at them, before trying solve issue with router-view:
Using Vue.bus
Using Vuex
Related
I have a parent component containing three child components. The first child component is a form. On a submit event it passes data to both the second and third child components via the parent component using props. However in one of the child components, the prop is always undefined. I think it's a timing issue, but using v-if does not seem to solve the issue.
The Parent Component:
<template>
<div>
<patents-searchform v-on:form-submit="processForm"></patents-searchform>
<patents-word-cloud
v-if="searched"
v-show="searched"
:patentsQuery="patentsQuery"
:livePage="livePage"
v-on:pageChange="handlePageChange"
/>
<patents-search-results
v-if="searched"
v-show="searched"
ref="resultsRef"
:livePage="livePage"
:results="patentsQueryResult"
v-on:pageChange="handlePageChange"
</div>
</template>
export default {
data() {
return {
livePage: 1,
searched: false,
queryform: 'initVal',
patentsQueryResult: {},
searching: false,
patentsQuery: {}
};
},
components: {
'patents-searchform': PatentsSearchForm,
'patents-searchresults': PatentsSearchResults,
'patents-word-cloud': PatentsWordCloud,
},
methods: {
handlePageChange(value) {
console.log('Homepage::handlePageChange', value)
this.queryform.page = value;
this.livePage = value;
this.fetchData();
},
processForm(formData) {
this.queryform = formData;
this.fetchData();
this.patentsQuery['query'] = this.queryform['query']
this.patentsQuery['searchMode'] = this.queryform['searchMode']
this.searched = true;
},
fetchData() {
const path = '/flask/searchPatentsNEW';
this.searching = true;
if (this.queryform !== 'initVal') {
axios.post(path, this.queryform)
.then((res) => {
this.patentsQueryResult = res.data;
this.searching = false;
})
.catch((error) => {
console.log(error);
});
}
}
}
};
The child component (PatentSearchResults) in which the props work correctly:
<template>
<b-container>
<b-card>
<a id="sTitleCard">Search Results</a>
<div id="quickStats" style="margin-left: 5%" v-if="this.results.stats">
{{results.stats.totalAuthors}} inventors across {{results.stats.docCount}} patents
({{results.stats.totalQueryTime}} seconds)
</div>
</b-card>
</b-container>
</template>
<script>
export default {
name: 'Results',
props: ['results', 'livePage'],
computed: {
thisAuthorPage() {
if (this.results.authors !== null) {
return this.results.authors; //this.results works fine
}
console.log('no authors')
return [];
},
},
methods: {
},
};
</script>
And the child component where the props are undefined:
<template>
<div>
<b-card id="patentWordCloudCard" bg-variant="light">
<b-container>
<b-form id="queryForm" #submit="onSubmit" #reset="onReset" novalidate>
<b-row class="d-flex flex-row-reverse">
<b-button id="btnSubmit" type="submit" variant="primary">Generate query word cloud</b-button>
</b-row>
</b-form>
</b-container>
</b-card>
</div>
</template>
<script>
export default {
name: 'Patents Word Cloud',
props: ['patentsQuery'],
data() {
return{
form: {
query: this.patentsQuery.query,
searchMode: this.patentsQuery.searchMode
},
show: true,
}
},
mounted() {
console.log(this.patentsQuery) //undefined
},
computed() {
console.log(this.patentsQuery) //undefined
},
methods: {
onSubmit(evt) {
evt.preventDefault();
console.log(this.patentsQuery) //undefined
}
}
}
</script>
Is it a timing issue where the word cloud component is mounted before patentsQuery is defined? If so, why did v-if not delay the component, as searched is false until after patentsQuery is defined.
It was a timing issue, I was able to access patentsQuery with the following code:
<template>
<div>
<b-card id="patentWordCloudCard" bg-variant="light">
<b-container>
<b-form id="queryForm" align-h="center" #submit="onSubmit" #reset="onReset" novalidate>
<b-row align-h="center">
<b-button align-h="center" id="btnSubmit" type="submit" variant="primary">Generate query word cloud</b-button>
</b-row>
</b-form>
</b-container>
</b-card>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'Patents Word Cloud',
props: ['patentsQuery'],
methods: {
onSubmit(evt) {
evt.preventDefault();
const path = 'flask/patentWordCloud';
this.searching = true
axios.post(path, this.patentsQuery).then((res) => {
console.log('Patent Word Cloud Data')
console.log(res.data)
this.patentResults = res.data;
})
.catch((error) => {
console.log(error)
})
}
}
}
</script>
so I have a json file which contains a bunch of visual test results and I would like to have a column in the data table for each row which have a dynamic path for a vue page that showcases the details for each test.I've got most of it figured out except the dynamic routing and passing parameters to the dynamic page.Here's the datatable component that I created (I'm using vuetify and nuxt btw)
<template>
<div>
<v-text-field
v-model="search"
label="Search"
class="mx-4"
></v-text-field>
<v-data-table
:headers="headers"
:items="tests"
:items-per-page="15"
:search="search"
class="elevation-1"
>
<template v-slot:[`item.status`]="{ item }">
<v-chip
:color="getColor(item.status)"
dark
>
{{ item.status }}
</v-chip>
</template>
<template #[`item.details`]="{ item }">
<nuxt-link :to="`tests/${item.name}`">show details</nuxt-link>
</template>
</v-data-table>
</div>
</template>
<script>
import testtable from '../../VisualTests/test_07_03_2022_10_13_48/Tests_results.json';
export default {
data() {
return {
search: '',
tests: testtable,
headers: [
{
text: 'Test tag',
align: 'start',
sortable: true,
value: 'tag',
},
{ text: 'testname', value: 'name' },
{ text: 'status', value: 'status' },
{ text: 'details', value: 'details' },
]
}
},
methods: {
getColor (status) {
if (status=="failed") return 'red'
else if (status=="skipped/pending") return 'blue'
else return 'green'
}
}
}
</script>
<style lang="scss" scoped>
</style>
this is my nuxt router file :
import Vue from 'vue'
import Router from 'vue-router'
import { normalizeURL, decode } from 'ufo'
import { interopDefault } from './utils'
import scrollBehavior from './router.scrollBehavior.js'
const _05a6b87a = () => interopDefault(import('..\\pages\\tests\\_name\\index.vue' /* webpackChunkName: "pages/tests/_name/index" */))
const _f1328bfa = () => interopDefault(import('..\\pages\\index.vue' /* webpackChunkName: "pages/index" */))
const emptyFn = () => {}
Vue.use(Router)
export const routerOptions = {
mode: 'history',
base: '/',
linkActiveClass: 'nuxt-link-active',
linkExactActiveClass: 'nuxt-link-exact-active',
scrollBehavior,
routes: [{
path: "/tests/:name",
component: _05a6b87a,
name: "tests-name"
}, {
path: "/",
component: _f1328bfa,
name: "index"
}],
fallback: false
}
export function createRouter (ssrContext, config) {
const base = (config._app && config._app.basePath) || routerOptions.base
const router = new Router({ ...routerOptions, base })
// TODO: remove in Nuxt 3
const originalPush = router.push
router.push = function push (location, onComplete = emptyFn, onAbort) {
return originalPush.call(this, location, onComplete, onAbort)
}
const resolve = router.resolve.bind(router)
router.resolve = (to, current, append) => {
if (typeof to === 'string') {
to = normalizeURL(to)
}
return resolve(to, current, append)
}
return router
}
I want to pass the testname,tag and status to the dynamic page.
So far, I have only been able to pass 1 parameter(name).
my dynamic page is in a folder named tests inside pages with a nested '_name' folder that contains index.vue.
How can I pass all the parameters ?
I have difficult to use vuex global state combine with re-render child-component in Vue.js.
The global state is mutated but does not re-render its data in v-for loop.
All list of data is rendered, but when the new data changes, component in /blog does not change data in it.
Here is some code:
/store/index.js
export const state = () => ({
allData: [],
})
export const getters = {
getAllData: (state) => state.allData,
}
export const mutations = {
GET_DATAS(state, payload) {
state.allData = payload
},
UPDATE_DATA(state, payload) {
const item = state.allData[payload.index]
Object.assign(item, payload)
},
}
export const actions = {
getDatas({ commit, state }, payload) {
return fetch(`URL_FETCH`)
.then((data) => data.json())
.then((data) => {
commit('GET_DATAS', data)
})
.catch((err) => console.log(err))
},
updateData({ commit, state }, payload) {
commit('UPDATE_DATA', payload)
},
}
in /layouts/default.vue
beforeCreate() {
this.$store.dispatch('getDatas').then(() => {
connectSocket()
})
},
methods: {
connectSocket() {
// connect & received message from socket
// received message from socket
this.$root.$emit('updateData', {
index: 12,
price: 34,
change: 56,
percent: 78,
})
},
},
and in /pages/blog/index.vue
<template>
<div>
<div
v-for="index in getAllData"
:key="index.name"
class="w-100 grid-wrapper"
>
<div>{{ index.price }}</div>
<div>{{ index.change }}</div>
<div>{{ index.percent }}</div>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
data() {
return {}
},
computed: {
...mapGetters(['getAllData']),
},
mounted() {
this.$root.$on('updateData', (item) => {
this.$store.dispatch('updateData', {
index: item.index,
price: item.price,
percent: item.percent,
change: item.change,
})
})
},
}
</script>
Here is a complete example on how to use Vuex and load the data efficiently into a Nuxt app (subjective but using good practices).
/pages/index.vue
<template>
<div>
<main v-if="!$fetchState.pending">
<div v-for="user in allData" :key="user.id" style="padding: 0.5rem 0">
<span>{{ user.email }}</span>
</div>
</main>
<button #click="fakeUpdate">Update the 2nd user</button>
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex'
export default {
data() {
return {
mockedData: {
name: 'John Doe',
username: 'jodoe',
email: 'yoloswag#gmail.com',
phone: '1-770-736-8031 x56442',
website: 'hildegard.org',
},
}
},
async fetch() {
await this.setAllData()
},
computed: {
...mapState(['allData']),
},
methods: {
...mapActions(['setAllData', 'updateData']),
fakeUpdate() {
this.updateData({ index: 1, payload: this.mockedData })
},
},
}
</script>
/store/index.js
import Vue from 'vue'
export const state = () => ({
allData: [],
})
export const mutations = {
SET_ALL_DATA(state, payload) {
state.allData = payload
},
UPDATE_SPECIFIC_DATA(state, { index, payload }) {
Vue.set(state.allData, index, payload)
},
}
export const actions = {
async setAllData({ commit }) {
try {
const httpCall = await fetch('https://jsonplaceholder.typicode.com/users')
const response = await httpCall.json()
commit('SET_ALL_DATA', response)
} catch (e) {
console.warn('error >>', e)
}
},
updateData({ commit }, { index, payload }) {
commit('UPDATE_SPECIFIC_DATA', { index, payload })
},
}
On the change of the value id, I would like to make a JSON call via Axios and update necessary parts of the page. How do I do that? Currently, I have mounted and activated and they do not seem to be working...
Code:
const Home = {
template: `
<div class="user">
<h2>user {{ id }}</h2>
<h2>{{ info }}</h2>
bet
</div>
`,
props: {
id: {
type: String,
default: 'N/A'
}
},
data () {
return {
info: null
}
},
activated () {
axios
.get('https://api.coindesk.com/v1/bpi/currentprice.json',
{ params: { id: id }}
)
.then(response => (this.info = response))
},
mounted() {
axios
.get('https://api.coindesk.com/v1/bpi/currentprice.json')
.then(response => (this.info = 'response'))
}
}`
You can listen to id prop change by using watch:
watch: {
id: function(newId) {
axios
.get('https://api.coindesk.com/v1/bpi/currentprice.json',
{ params: { id: newId }}
)
.then(response => (this.info = response))
}
}
Here is a little demo based on the code that you shared that shows how watch reacts to id prop change. Wrapper component below is solely for demonstration purpose as something that triggers id value change.
const Home = {
template: `
<div class="user">
<h2>user {{ id }}</h2>
<h2>{{ info }}</h2>
bet
</div>
`,
props: {
id: {
default: 'N/A'
}
},
data () {
return {
info: null
}
},
mounted() {
axios
.get('https://api.coindesk.com/v1/bpi/currentprice.json')
.then(response => (this.info = 'response'))
},
watch: {
id: function(newId) {
console.log(`watch triggered, value of id is: ${newId}`);
axios
.get('https://api.coindesk.com/v1/bpi/currentprice.json',
{ params: { id: newId }}
)
.then(response => (this.info = response))
}
}
}
const Wrapper = {
template: '<div><home :id="id" /></div>',
components: { Home },
data() {
return {
id: 0
}
},
mounted() {
const limit = 5;
const loop = (nextId) => setTimeout(() => {
console.log(`#${nextId} loop iteration`);
if (nextId < limit) {
this.id = nextId;
loop(nextId + 1);
}
}, 3000);
loop(this.id);
}
}
new Vue({
el: '#app',
components: { Wrapper }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js" ></script>
<div id="app">
<wrapper />
</div>
I'm trying to change the state of my Vuex Store with the mutations, and I also have routes in my application. In my default component (the first route) has a button that changes my user.name state, and I have a second component (the second route) that show to me the user.name. In my first component, when I call the mutation to change the user.name state it shows to me the new user.name, but this doesn't become different of the first state.
For example: In my first user.name state I have "John Dave" and when I click on the button that calls the mutation CHANGE_USER(), this has to change to "David Lionel", but don't work.
Store
export default new Vuex.Store({
state: {
user: {
name: 'John Dave',
},
},
mutations: {
changeUser (state, payload) {
state.user = payload;
}
},
getters: {
getName(state) {
return state.user;
}
}
});
First component
<template>
<div>
{{user}}
</div>
</template>
<script>
export default {
computed: {
user() {
const { name } = this.$store.getters.getName;
return name;
},
},
};
</script>
Second component
<template>
<div>
{{user}}
<button #click="changeUser() ">
Change user
</button>
</div>
</template>
<script>
export default {
computed: {
user() {
const { name } = this.$store.state.user;
return `My user is ${name}`
},
},
methods: {
changeUser() {
const payload = {
name: 'Name changed',
email: 'vitor#v.cm',
level: 'ADM',
};
this.$store.commit('changeUser', payload);
},
}
};
</script>