How Vue3 pass dynamic segemet to nested router? - vue.js

Coding Enviroment: Vue3, JavaScript
Router code:
{
path: '/group/:id',
component: Group,
children: [
{
path: 'homework_list',
component: HomeworkList,
},
{
path: 'member_list',
component: GroupMemberList
},
]
},
Vue code:
<router-link to="homework_list">Homework List</router-link>
First I can visit the link http://localhost:3333/group/1, when I click the router-link, I want to go a URL like http://localhost:3333/group/1/homework_list, but it just go to the URL http://localhost:3333/group/homework_list.
I know I can change the router-link like following code to implement the function. But the id is not static.Is it necessary to add the id value of current link to all route-link? It seems a bit troublesome.
<router-link to="1/homework_list">Homework List</router-link>
My English is not so good, if there is any inappropriate expression, please forgive me.

to property of router-link is just like any other property of your custom components and as such, it can be dynamic. For example, let's assume that you have groupId property in your data
<template>
<div>
<router-link :to="groupId + '/homework_list'">Homework list</router-link>
</div>
</template>
<script>
export default {
data () {
return {
groupId: 15
}
}
</script>
Note that there is a big difference between to="foo", and :to="foo". The first one is static (string foo) and second one is dynamic (referencing property named foo) - meaning that every time that property changes it will be propagated to the child components.
You can read about it further here

Related

vue v-for: translate with switch in vue i18n

I have a problem to translate the information with vue i18n that is called through "v-for", all the data from the template is translated without problem, but those that I export through arrays and scripts do not render
i am using vue-i18n: version 7.8.1
You're only ever setting the helpTitles property when your component is created.
I would suggest using $t() in your templates instead of within data(). Then it will automatically react to changes.
I honestly don't think using an array from the translation file is a great idea. I'd be more inclined to add them with their own keys, just like your question and info translation keys, eg
helpStartedTitle: "GETTING STARTED - MODEL",
helpMembersTitle: "MEMBERS",
helpAccountTitle: "ACCOUNT",
//etc
You could then set up the keys in your data like this
data: () => {
const keys = [
"helpStarted",
"helpMembers",
"helpAccount",
"helpPayment",
"helpSocial",
"helpFraud",
"helpSupport",
"helpStudio"
]
return {
helpInfo: keys.map((key, id) => ({
id,
title: `general.help.${key}Title`,
question: `general.help.${key}`,
answer: `general.help.${key}Info`
}))
}
}
then in your template
<div v-for="help in helpInfo" :key="help.id">
<div :id="help.id" class="help-subtitle">{{ $t(help.title) }}:</div>
<HelpItem
:question="$t(help.question)"
:answer="$t(help.answer)"
:num="help.id"
/>
</div>
Even better would be to just pass the translation keys through to your HelpItem component and use $t() with it.
<HelpItem
:question="help.question"
:answer="help.answer"
:num="help.id"
/>
and in the HelpItem component
export default {
name: "HelpItem",
props: {
question: String,
answer: String,
num: Number
},
// etc
}
<!-- just guessing here -->
<h1>{{ $t(question) }}</h1>
<p>{{ $t(answer) }}</p>
FYI, I've corrected answear to answer throughout.

Vue.js router-link params expected to be defined but getting browser error "missing param for named route <route_name>: expected <param> to be defined

Hi I'm trying to use a Vue.js router-link to pass a parameter dynamically into a component. I'm getting a browser error that says that the parameter is not defined, even though the router URL is being displayed correctly, with the parameter. The component is also rendered, but the data does not seem to be accessible in the component as a property or data element.
Here is my table with router-link:
<sui-table-row v-for="(player, i) in allPlayers" :key="i">
<sui-table-cell>{{player.league}}{{i+1}}</sui-table-cell>
<sui-table-cell>{{ player.player1 }}&{{player.player2}}</sui-table-cell>
<sui-table-cell selectable v-for="(week, j) in allDates" :key="j">
<router-link :to="{ name: 'addWeek', params: {team1: player._id } }">
{{player.schedule[j]}}
</router-link>
</sui-table-cell>
</sui-table-row>
Routes.js
import AddWeek from '#/views/admin/addWeek.vue';
const routes = [{
path: '/ncl-schedule/addWeek/:team1',
name: 'addWeek',
component: AddWeek
}]
export default routes
Two issues I'm having here:
1: Chrome is throwing the following error:
[vue-router] missing param for named route "addWeek": Expected "team1" to be defined
This is odd because I get the team id passed as the param on the route, and it brings me to the AddWeek component as I'd expect.
http://localhost:8080/ncl-schedule/addWeek/5e6bc31785aa8e5ab4575d80
2: I can't get the data as a property or data element in the component, however.
The error indicates that player _id isn't ready when the component is first rendered, maybe it's async data. Try a v-if on your link:
<router-link v-if="player && player._id" :to="{ name: 'addWeek', params: {team1: player._id } }">
Params are accessed at this.$route.params in the target component. You can automatically convert those to props if you prefer, by defining your route with the props: true option:
const routes = [{
path: '/ncl-schedule/addWeek/:team1',
name: 'addWeek',
component: AddWeek,
props: true
}]
You need to also create the team1 prop in addWeek.
Click Schedule Link Here to View Example
With regard to the initial Chrome error, Dan was correct that the data was async and therefore not rendered upon initial load. This is resolved now in this solution.
I ultimately wanted multiple parameters to be passed in from the component. In order to accomplish this I used "query" opposed to "params", in the "router-link". The rest of the code block is included for clarification of the parameters being passed.
Parent Component:
<sui-table-row v-for="(players, i) in allPlayers" :key="i">
<sui-table-cell>{{players.league}}{{players.teamNumber}}</sui-table-cell>
<sui-table-cell>{{ players.player1 }}&{{players.player2}}</sui-table-cell>
<sui-table-cell selectable v-for="(week, j) in allDates" :key="j">
**<router-link :to="{ name: 'addWeek', query: {selectedTeam: players, week: week.weekId, match: players.schedule[j] } }">
{{players.schedule[j]}}
</router-link>**
</sui-table-cell>
</sui-table-row>
routes.js
const routes = [{
path: '/schedule/addWeek',
props(route) {
return route.query || {}
},
name: 'addWeek',
component: AddWeek
}]
Finally within the component I wanted to access the data I retrieved them from the route within the mounted hook as follows:
Consuming Component:
async mounted() {
// this is the team in the players field who's week's matches were chosen.
this.selectedTeam = this.$route.query.selectedTeam
// teams clicked on
this.teamsPlayed = this.$route.query.match
// this is the week the matches take place - intersection of selectedTeam, date
this.schedule.weekId = this.$route.query.week
}

Nuxt js - passing object params in dynamic route

I'm using the following link to dynamic routes
<nuxt-link :key="$route.fullPath" :to="{ name: 'items-id', params: { parent: { id: item.parent.id, description: item.parent.description } }}">Click me</nuxt-link>
So navigating to /items/ correctly passes the specified params. But if i click another nuxt-link with different params, while being on the /items/ url then nothing happens. I imagine it's because the url doesn't really change.
What's the best way to "reload" the url with new params?
I've considered using path which would be different for each nuxt-link
<nuxt-link :key="$route.fullPath" :to="{ path: '/items/' + item, params: { parent: { id: item.parent.id, description: item.parent.description } }}">Click me</nuxt-link>
But this makes the URL ugly as it includes the object ref
Just watch() the params (probably the id) and reload data when they change:
watch: {
id() {
// reload your item here
},
...
},
Using watch should do it. Just update the variable you want to change according to the value for example if you want to watch for the id variable then set it to a component variable that you will want to be displayed.
watch: {
id (value) {
this.VARIABLE = value
}
}

Declaring Reactive Properties (Adding component blocks dynamically from array push)

So, I'm attempting to create a Gutenburg style blog, I'm working on a block creation method. I should caveat - this is entirely my method, if this is the wrong method - great, let me know, but please let me know the correct way! :)
Back to the question. In my project I have the following component.
baseComponent.vue:
<template>
<component v-for="contentBlock in contentBlocks" v-bind:is="contentBlocks.blockComponent" v-bind:key="contentBlock.id" transition="fade" transition-mode="out-in"></component>
</template>
<script>
import CodeBlockComponent from './codeBlockComponent';
export default {
name: 'BaseComponent',
components: {
CodeBlockComponent <!-- Corresponds to the name given in './codeBlockComponent'
},
data: () => ({
contentBlocks: []
})
watch: {
contentBlocks () {
console.log(this.contentBlocks)
}
},
methods: {
addCodeBlock () {
console.log('Code Block Added!')
this.contentBlocks.push({ 'id': this.contentBlocks.length + 1, 'blockType': 'code', 'blockComponent': 'CodeBlockComponent', 'content': '' })
},
addQuoteBlock () {
console.log('Quote Block Added!')
this.contentBlocks.push({ 'id': this.contentBlocks.length + 1, 'blockType': 'quote', 'content': '' })
}
}
}
</script>
N.B. Where above I have stripped the complexity from my template.
Within this same baseComponent I also have buttons which add blocks to the contentBlocks array, where my watch method is definitely finding blocks when added to this array (see below for a screenshot of the console output):
So, everything seems to be going ok - I'm now ready to add block Components. I add the first one, and I receive the following error in the console:
vue.runtime.esm.js?2b0e:587 [Vue warn]: Property or method "CodeBlockComponent" 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.
I took one look at the documentations where it advised to head to, which was here: https://v2.vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties
An example of the CodeBlockComponent.vue:
<template>
<div>
<p>HELLO WORLD FROM THE CODE BLOCK!!</p>
</div>
</template>
<script>
export default {
name: 'CodeBlockComponent',
data: () => ({
}),
computed: {
},
watch: {
},
methods: {
}
}
</script>
I took one look, and I'll be 100% honest - I don't quite understand what it is telling me to do...I feel like I have declared a reactive property? Any advise or pointers anyone can give me would be greatly appreciated!
I'm assuming I can't simply import CodeBlockComponent from './codeBlockComponent'; for this sort of dynamic rendering of components? (But, I really don't know at this point)...
I think what you described should work just fine in case if you've imported and declared in
components: { ... }
all possible names which could be found in each contentBlock.blockComponent. Notice those components must have their name: <String> exactly the same as in your contentBlock.blockComponent. I can't see in your example this prop for item from addQuoteBlock by the way.
You also provided link on documentation, but it's about props, which works just fine in your example. Recheck section about dynamic components, maybe it will help: https://v2.vuejs.org/v2/guide/components.html#Dynamic-Components (notice links on fiddles)
One more thing to check: does your component in baseComponent.vue wrapped with some ? Component must have single root element. Component with v-for probably won't go.

Vue 2 pass props to child [old : "call child's method"]

ok so I've learned that I'm not supposed to call a child's method but pass it props instead.
I've got (parent) :
<template>
<div id="main">
<Header :title ="title"/>
<router-view/>
<LateralMenu/>
</div>
</template>
<script>
export default {
name: 'app'
data: function () {
return {
title: true
}
},
methods: {
hideTitle: function () {
this.title = false
console.log(this.title)
},
showTitle: function () {
this.title = true
console.log(this.title)
}
}
}
</script>
and (child) :
<script>
export default {
name: 'Header',
props: ['title'],
created () {
console.log(this.title)
},
methods: {
}
}
</script>
the first console logs (inside the parent) print correctly on each method but the second console log within the child stays true all the time. I got this from : Pass data from parent to child component in vue.js
inside what method does the console.log need to be to be printed everytime the methods in the parent are triggered?
(this is why I wanted to go for method-calling, originally, by going with variables instead, we're potentially omitting valuable parts of the process such as optimization and a "when" for the execution(s!!) of our code. pontetally being the key word here, don't blow up on me, keep in mind that I'm learning.)
OLD:
I've browsed the web and I know there a a million different answers
and my point is with the latest version of vue none of those millions
of answers work.
either everything is deprecated or it just doesn't apply but I need a
solution.
How do you call a child method?
I have a 1 component = 1 file setup.
DOM is declared inside a <template> tag javascript is written inside
a <script> tag. I'm going off of vue-cli scaffolding.
latest method I've tried is #emit (sometimes paired with an #on
sometimes not) doesn't work :
child :
<script>
export default {
name: 'Header',
created () {
this.$on('hideTitlefinal', this.hideTitlefinal)
},
methods: {
hideTitlefinal: function () {
console.log('hideeeee')
},
showTitlefinal: function () {
console.log('shwowwww')
}
}
}
</script>
parent :
<template>
<div id="main">
<Header v-on:hideTitle="hideTitlefinal" v-on:showTitle="showTitlefinal"/>
<router-view/>
<LateralMenu/>
</div>
</template>
<script>
export default {
methods: {
hideTitle: function () {
this.$emit('hideTitle')
},
showTitle: function () {
this.$emit('showTitle')
}
}
}
</script>
console :
Uncaught TypeError: this.$emit is not a function
at Object.showTitle (Main.vue?1785:74)
at VueComponent.showTitle (LateralMenu.vue?c2ae:113)
at boundFn (vue.esm.js?efeb:186)
at invoker (vue.esm.js?efeb:1943)
at HTMLDivElement.fn._withTask.fn._withTask (vue.esm.js?efeb:1778)
Please don't do this. You're thinking in terms of events. When x happens, do y. That's sooo jquery 2005 man. Vue still has all that stuff, but we're being invited to think in terms of a view model...
You want your state in a variable, in window scope, and you want reactive pipes linking your vue stuff to your state object. To toggle visibility, use a dynamic class binding, or v-if. Then think about how to represent your state. It could be as simple as having a property like store.titleVisible. But, you want to 'normalize' your store, and avoid relationships between items of state. So if title visibility really depends on something higher up, like an editMode or something, then just put the higher-up thing in the store, then create computed properties if you need them.
The goal is that you don't care when things happen. You just define the relationships between the markup and the store, then let Vue take care of it. The docs will tell you to use props for parent=>child and $emit for child=>parent communication. Truth is you don't need this until you have multiple instances of a component, or reusable components. Vue stuff talks to a store, not to other vue stuff. For single-use components, as for your root Vue, just use the data:.
Whenever you find yourself writing show/hide methods, you're doing it wrong. It's intuitive (because it's procedural), but you'll quickly appreciate how much better the MVVM approach is.