Using Vue3 and vue-router4, two different components share the same child components. The component templates are setup as follows:
<!-- Component A -->
<template>
<ComponentA>
<Child />
</ComponentA>
</template>
<!-- Component B -->
<template>
<ComponentB>
<Child />
</ComponentB>
</template>
<!-- Child -->
<template>
<FilterResults />
</template>
These are the configured routes:
const routes = [
{
path:'/componenta/:param',
component: ComponentA
},
{
path:'/componentb',
component: ComponentB
}
]
const router = createRouter({
history: createWebHistory(),
routes,
})
Some data is setup in the Child component:
export default {
name: 'Child'
...,
data() {
return {
filters: {
size: [
{
selected: this.$route.query.size === "1m"
}
]
}
}
}
}
The above aims to set selected to true or false depending on whether a match is found in the route. The results is then passed into FilterResults as a prop:
<!-- Component A -->
<template>
<ComponentA>
<Child />
</ComponentA>
</template>
<!-- Component B -->
<template>
<ComponentB>
<Child />
</ComponentB>
</template>
<!-- Child -->
<template>
<FilterResults :filters="filters" />
</template>
With the above, the value of selected in the filter data is evaluated and the intended result is that when the components load, the filters in the $route are set to true in the data.
The problem is, where the child components of ComponentA and ComponentB are identical:
ComponentA /componenta/xyz?size=1m does not work as intended, where matches found in the route are not set to true in the filters data.
ComponentB /componentb?size=1m does work as intended, where matches found in the route are set to true in the filters data.
I can reproduce the problem only if router-view isn't keyed, so I'm assuming that's what you have.
If there are two router-links to the same component but with different size query parameters (as shown below), and you're clicking one link and then the other, Vue reuses the existing component instance, so the component's data() is not invoked, and the query parameter is not re-evaluated.
<router-link to="/componenta/xyz?size=1m">A (size=1m)</router-link> |
<router-link to="/componenta/xyz?size=0">A (size=0)</router-link> |
To ensure a new component is created for each route change, specify a router-view key on $route.fullPath (which includes the query parameters):
<router-view :key="$route.fullPath"></router-view>
demo
In my opinion, the problem is here, your data is not recalculating on route change,
try to modify local data on route change. Try to add debugger before return statement in data, it will come only one even if change the route.
Related
I'm trying to set up a parent-child hierarchy here are the relevant component definitions - I've simplified them to just show the structure of the hierarchy.
RootComponent.vue
<template>
<ComponentA></ComponentA>
</template>
ComponentA.vue
<template>
<!--...-->
<ComponentB></ComponentB>
</template>
ComponentB.vue
<template>
<!--Some standard html tags-->
<ComponentA></ComponentA>
<!--...-->
<ComponentC></ComponentC>
</template>
ComponentC.vue
<template>
<!--Some standard HTML tags-->
<ComponentD></ComponentD>
</template>
The problem is that ComponentA within ComponentB is not rendered and the following error message is displayed in the console:
Unknown custom element: - did you register the component correctly? For recursive components, make sure to provide the "name" option.
The top-level instance of ComponentA and ComponentB render correctly it is only the nested ComponentA one level down that does not render.
All of the components are created using the Vue.extend() method and have a name property defined on them. The components are imported by a components: property
ComponentA imports ComponentB
ComponentB imports ComponentA and ComponentC
Edit
Component A Importing
import ComponentB from "./ComponentB.vue"
export default Vue.extend({
name: "ComponentA",
//...
components: {
ComponentB
}
})
Component B Importing
import ComponentA from "./ComponentA.vue"
import ComponentC from "./ComponentC.vue"
export default Vue.extend({
name: "ComponentB",
//...
components: {
ComponentA,
ComponentC
}
})
Expected HTML Structure Rendered
<ComponentA>
<ComponentB>
<ComponentA>
<ComponentB>
<ComponentC></ComponentC>
</ComponentB>
</ComponentA>
<ComponentC></ComponentC>
</ComponentB>
</ComponentA>
Actual HTML Structure Rendered
<ComponentA>
<ComponentB>
<!--ComponentA is missing here-->
<ComponentC></ComponentC>
</ComponentB>
</ComponentA>
I've spent hours searching online for solutions but can't find any that solve the issue. Any pointers are greatly appreciated.
I'm a bit new to the component's world and trying to figure out a thing, how the parent child relationship works in components. I've seen some examples of some component libraries where they have some parent child components to be defined and those are used as the child components. For example, table and tr:
<my-table> <!-- Parent -->
<my-tr> </my-tr> <!-- Child -->
</my-table>
Now, I assume, that child works for parent via slots. So the parent should be defined something like this:
<template>
<div>
<slot></slot>
</div>
</template>
Now the parent element can have multiple <my-tr> as well. And slot should be rendering all of those. However, I am trying to a similar thing but a little more complex than that.
I am trying to create a slider with this approach. Where there is a my-slider component and my-slider-item components used to define inside my-slider component. And then I want to control the visibility of the child components defined in the parent component slot by modifying it's properties.
It should be looking like this:
<my-slider>
<my-slider-item>Item 1</my-slider-item>
<my-slider-item>Item 2</my-slider-item>
<my-slider-item>Item 3</my-slider-item>
</my-slider>
my-slider component
<template>
<div class="my-slider">
<slot></slot>
</div>
</template>
my-slider-item component
<template>
<div class="my-slider__item">
<slot></slot>
</div>
</template>
Now how can I know in the parent that how many <my-slider-item> are defined in the parent slot and based on that I want to control the visibility of the child 1 at a time as it is going to work as the slider.
I'm not sure but I missing some basic concept here which I was not getting after looking at tons of example since yesterday. If anyone can please help here? Thanks a lot!
The parent-child relationship is actually established by importing the child component into the parent component, and including the child in the parent's 'components' option.
I created an example scenario with simple Parent and Child component definitions in order to show a standard relationship implementation. Built with Vue 2 and the Vue CLI.
MySlider.vue (parent)
<template>
<div class="my-slider">
<h4>My Slider</h4>
<my-slider-item v-for="(item, index) in sliderItems" :key="index" :sliderItem="item" />
</div>
</template>
<script>
import MySliderItem from './MySliderItem.vue'
export default {
components: {
MySliderItem
},
data() {
return {
sliderItems: [
{
name: 'Slider Item 1'
},
{
name: 'Slider Item 2'
},
{
name: 'Slider Item 3'
}
]
}
}
}
</script>
MySliderItem.vue (child)
<template>
<div class="my-slider-item">
<h5>{{ sliderItem.name }}</h5>
</div>
</template>
<script>
export default {
props: {
sliderItem: {
type: Object,
required: true
}
}
}
</script>
How can I write my component to wrap another vue component, while my wrapper component get some extra props? My wrapper template component should be:
<wrapper-component>
<v-table></v-table> <!-- pass to v-table all the props beside prop1 and prop2 -->
</wrapper-component>
and the wrapper props:
props: {
prop1: String,
prop2: String
}
Here I want to wrap a table component, and pass to the table component all the props and events that were passed to the wrapper, beside two extra props prop1 and prop2. What is the correct way of doing this in vue?
And is there a solution for events too?
Place the component you wish to wrap into the template of the wrapper component, add v-bind="$attrs" v-on="$listeners" to that component tag, then add the inner component (and, optionally, inheritAttrs: false) to the wrapper component's config object.
Vue's documentation doesn't seem to cover this in a guide or anything, but docs for $attrs, $listeners, and inheritAttrs can be found in Vue's API documentation. Also, a term that may help you when searching for this topic in the future is "Higher-Order Component" (HOC) - which is basically the same as your use of "wrapper component". (This term is how I originally found $attrs)
For example...
<!-- WrapperComponent.vue -->
<template>
<div class="wrapper-component">
<v-table v-bind="$attrs" v-on="$listeners"></v-table>
</div>
</template>
<script>
import Table from './BaseTable'
export default {
components: { 'v-table': Table },
inheritAttrs: false // optional
}
</script>
Edit: Alternatively, you may want to use dynamic components via the is attribute so you can pass in the component to be wrapped as a prop (closer to the higher-order component idea) instead of it always being the same inner component. For example:
<!-- WrapperComponent.vue -->
<template>
<div class="wrapper-component">
<component :is="wraps" v-bind="$attrs" v-on="$listeners"></component>
</div>
</template>
<script>
export default {
inheritAttrs: false, // optional
props: ['wraps']
}
</script>
Edit 2: The part of OP's original question that I missed was passing all props EXCEPT one or two. This is handled by explicitly defining the prop on the wrapper. To quote the documentation for $attrs:
Contains parent-scope attribute bindings (except for class and style) that are not recognized (and extracted) as props
For example, example1 is recognized and extracted as a prop in the snippet below, so it doesn't get included as part of the $attrs being passed down.
Vue.component('wrapper-component', {
template: `
<div class="wrapper-component">
<component :is="wraps" v-bind="$attrs" v-on="$listeners"></component>
</div>
`,
// NOTE: "example1" is explicitly defined on wrapper, not passed down to nested component via $attrs
props: ['wraps', 'example1']
})
Vue.component('posts', {
template: `
<div>
<div>Posts component</div>
<div v-text="example1"></div>
<div v-text="example2"></div>
<div v-text="example3"></div>
</div>
`,
props: ['example1', 'example2', 'example3'],
})
new Vue({
el: '#app',
template: `
<wrapper-component wraps="posts"
example1="example1"
example2="example2"
example3="example3"
></wrapper-component>
`,
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>
I am using laravel + vue. I want to do title of page in navbar, so when you are in index, in navbar is text index. When you are in settings, navbar says settings etc.
I think props is good for it, but when I use that, it works not good. Look here:
blade.php of index:
#extends('layout.app')
#section('content')
<index :msg="msg"></index>
#endsection
index.vue:
props: [
'msg'
],
Now navbar:
<template>
<nav class="navbar">
<a></a>
<p>{{msg}}</p>
</nav>
</template>
<script>
export default {
props: [
],
data() {
return {
}
},
}
</script>
and layout:
<body>
<div id="app">
<navbar></navbar>
#yield('content')
</div>
</body>
How I can change that {{msg}} paragraph in navbar when we are on different pages? My code doesn't work.
[Vue warn]: Property or method "msg" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
If you want to use a prop then you need to define it in the props object of your component. In your NavBar you are referencing to msg, but the props object in NavBar is empty. Define it and pass the prop in your layout.blade.php.
Or you could also define a computed property where you take a look at the current route and return a string fitting your business.
https://v2.vuejs.org/v2/guide/computed.html
If you want to share data between multiple components, then use a store (VUEX) as proposed :)
I have actually 2 global components one for Admin and other one for Modal. The Admin component have a child comp called Page and the Page comp have others childs. I want to pass content directly to Page comp via slots. Like this:
app.js
new Vue({
el: '#app',
components: { Admin, Modal }
})
Admin.vue
<template>
<div>
<page>
<slot></slot>
</page>
</div>
</template>
export default {
components: { Page }
}
Page.vue
<template>
<div>
<page-header>
<slot name="page-header">
<h1 class="page-title">
<slot name="page-title">
Page Title
</slot>
</h1>
</slot>
</page-header>
<page-body>
<slot>
Page Body
</slot>
</page-body>
<page-footer>
<slot name="page-footer">
Page Footer
</slot>
</page-footer>
</div>
</template>
export default {
components: {
pageHeader,
pageBody,
pageFooter
}
}
index.html
<admin>
<div slot="page-header">
Header Test
</div>
Body Test
<div slot="page-footer">
Footer Test
</div>
</admin>
I don't need to use Page as global component, Please any idea?? Hope you understand what I'm looking for...
Thanks
I don't think slots are designed to do this. If you need data to be persisted between parent and children then use either props as #Belmin Bedak suggested. When you pass a prop to a component it will be available to all its children.
If you need to persist state on front-end I strongly recommend using Vuex as source of data for all components ( only use if it becomes more complex to have data scattered across components ).