I have component 'Page' that should display a component which is retrieved via its props.
I managed to get my component loads when I harcode my component path in my component data like this :
<template>
<div>
<div v-if="includeHeader">
<header>
<fv-header/>
</header>
</div>
<component :is="this.componentDisplayed" />
<div v-if="includeFooter">
<footer>
<fv-complete-footer/>
</footer>
</div>
</div>
</template>
<script>
import Header from '#/components/Header/Header';
import CompleteFooter from '#/components/CompleteFooter/CompleteFooter';
export default {
name: 'Page',
props: {
componentPath: String,
includeHeader: Boolean,
includeFooter: Boolean
},
data() {
componentDisplayed: function () {
const path = '#/components/my_component';
return import(path);
},
},
components: {
'fv-header': Header,
'fv-complete-footer': CompleteFooter,
},
}
</script>
But with the data I cannot refer to my props within my function as this is undefined.
I tried to used computed properties instead of data but I have the error "src lazy?0309:5 Uncaught (in promise) Error: Cannot find module '#/components/my_component'. But the module exists! But maybe not at that time ?
computed: {
componentDisplayed: function () {
const path = `#/components/${this.componentPath}`;
return import(path);
},
},
There must be away to deal with that but I am quite a beginner to vue.js :)
Instead of trying to import the component in your child component, instead import it in the parent component and pass the entire component as a prop.
<template>
<div :is="component" />
</template>
<script>
export default {
name: "page",
props: {
component: {
required: true
}
}
};
</script>
And in the parent
<page :component="component" />
and
import Page from './components/Page';
// and further down
data () {
return {
component: HelloWorld
}
}
Related
In Vue2 I'm trying to access child components' data and then put into parent component's data without triggering an event. In the following example I want to save count:20 into parent component, please tell me if there's any mistake, thanks!
Child Component
<template>
<div></div>
</template>
<script>
export default {
data() {
return {
count: 20,
};
},
};
</script>
Parent Component
<template>
<div>
<child ref="child1"></child>
{{count}}
</div>
</template>
<script> import child from './child.vue'
export default {
components: {
child
},
data() {
return{
count:this.$refs.child1.count
}
},
}
</script>
warn message in VScode
Property 'count' does not exist on type 'Vue | Element | Vue[] | Element[]'.
Property 'count' does not exist on type 'Vue'.
warn message in browser
[Vue warn]: Error in data(): "TypeError: undefined is not an object (evaluating 'this.$refs.child1')"
Let me preface with I would recommend using the Vue framework as intended. So passing data from a child to the parent should be done with $emit or using a vuex store for centralized state management.
With that out of the way you will want to wait until the parent component is mounted to set the count data attribute.
Child
<template>
<div></div>
</template>
<script>
export default {
data() {
return {
count: 20,
};
},
};
</script>
Parent
<template>
<div>
<child ref="child1"></child>
{{ count }}
</div>
</template>
<script>
import Child from "./components/Child";
export default {
components: {
Child
},
data() {
return{
count: 0
}
},
mounted () {
this.count = this.$refs.child1.count
}
};
</script>
This will work, however it WILL NOT BE reactive. This can all be greatly simplified AND made reactive with the following changes:
Child
<template>
<div></div>
</template>
<script>
export default {
data() {
return {
count: 20,
};
},
watch: {
count (currentValue) {
this.$emit('update', currentValue);
}
},
beforeMount () {
this.$emit('update', this.count)
}
};
</script>
Parent
<template>
<div>
<child #update="count = $event"></child>
{{ count }}
</div>
</template>
<script>
import Child from "./components/Child";
export default {
components: {
Child
},
data() {
return{
count: 0
}
}
};
</script>
Quick link to show a working example: https://codesandbox.io/s/interesting-kalam-et0b3?file=/src/App.vue
I have a component named ProductArea which displays products loaded from the Prismic API. The products loaded are dependant on a category which is selected by the user in a sidebar.
I'm using Vuex and struggling to come up with a flow that avoids a situation where category is not yet available in my store (category is also loaded from Prismic).
Here is what the parent of ProductArea looks like:
<template>
<div>
<NavBar />
<!-- <Header /> -->
<main>
<div v-if="!$fetchState.pending" class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex-1 min-w-0 bg-white xl:flex">
<Sidebar :navigation="navigation" />
<ProductArea />
</div>
</div>
</main>
</div>
</template>
<script lang="ts">
import Vue from 'vue'
import NavBar from '#/components/NavBar.vue'
import Sidebar from '#/components/Sidebar.vue'
import Header from '#/components/Header.vue'
import CategoryHeader from '#/components/CategoryHeader.vue'
import ProductGrid from '#/components/ProductGrid.vue'
import { mapActions } from 'vuex'
import { mapGetters } from 'vuex'
export default {
name: 'App',
components: {
Sidebar,
NavBar,
Header,
CategoryHeader
},
data() {
return {
navigation: null
}
},
async fetch() {
const component = this
await this.fetchCategories()
.then(function(navigationResult) {
const navigation = component.$store.getters.navigation
component.navigation = navigation
})
},
fetchOnServer: true,
methods: {
...mapActions({ fetchCategories: 'fetchCategories', fetchProducts: 'fetchProducts' })
}
}
</script>
I assumed having v-if="!$fetchState.pending" would prevent ProductArea from being created until category has been loaded into the store, however this doesn't seem to be the case.
Here is ProductArea:
<template>
<div class="bg-white lg:min-w-0 lg:flex-1">
<CategoryHeader :category="this.category" :products="this.products" />
<div class="sm:p-6">
<ProductGrid :category="this.category.primary.category" :products="this.products" />
</div>
</div>
</template>
<script lang="ts">
import { mapActions } from 'vuex'
import { mapGetters } from 'vuex'
import Locale from '#/types/locale'
export default {
name: 'ProductArea',
data() {
return {
category: this.$store.getters.category,
products: Array
}
},
async fetch() {
const component = this
await this.fetchProducts(this.category)
.then(function(productsResult) {
const products = component.$store.getters.products
component.products = products
console.log(products)
})
},
fetchOnServer: true,
methods: {
...mapActions({ fetchProducts: 'fetchProducts' })
}
}
</script>
Here's the error I'm receiving:
Error in fetch(): TypeError: Cannot read property 'products' of undefined
This error is referring to the undefined category within the fetchProducts called via fetch on the ProductsArea component.
Can anyone point me in the right direction? What would be the optimal flow here to prevent category being accessed before it is available?
You could set a default category. If you don't want to do that, bring the Vuex category into the parent and only show <ProductArea> when it's defined:
Parent
<ProductArea v-if="category" />
computed: {
...mapGetters(['category'])
}
This is necessary because your v-if on $fetchState.pending only tests whether all the categories are loaded, but for the child component you also need to test that a category has been selected.
In fact, you can simplify all your code by mapping the getters instead of storing getters in variables, which is not a good practice. Those variables wouldn't be updated reactively when the getter changes. Instead, completely remove the data options from both components:
Parent
async fetch() {
await this.fetchCategories();
}
computed: {
...mapGetters(['category', 'navigation'])
}
Child
async fetch() {
await this.fetchProducts();
}
computed: {
...mapGetters(['category', 'products'])
}
Other improvements:
You can shorten the mapActions calls a bit:
Parent: ...mapActions(['fetchCategories'])
Child: ...mapActions(['fetchProducts'])
In the Parent component I have:
<todo-item v-for="(todo, index) in todos" :key="todo.id" :todo="todo" :index="index">
</todo-item>
which just iterates through todos array and gets each todo object and by using props passes each Object and its index to the child component. todo-item registered in Child component.
todos is an array of objects:
todos: [
{
'id': 1,
'title': 'Object 1'
},
{
'id': 2,
'title': 'Object 2'
}
]
Child component:
<template>
<div class="todo-item">
<div class="todo-item-left">
<div>{{ todo.title }}</div>
</div>
</div>
</template>>
<script>
export default {
name: 'todo-item',
props: {
todo: {
type: Object,
required: true
},
index: {
type: Number,
required: true
}
}
}
</script>
I don't know why it doesn't render each todo on the page, I have a blank page. Even though in Vue DevTools It shows that I have these objects.
Did I miss something?
EDIT:
There is an error, sorry the error flag were off hence didn't saw it.
Error message:
[Vue warn]: Unknown custom element: - did you register the component correctly? For recursive components, make sure to provide the "name" option.
As you can see above I did register the component in Child component.
And yes I did import the child component in Parent component by doing:
//Parent component
import ToDoItem from './ToDoItem'
export default {
name: 'todo-list',
components: {
ToDoItem,
},
You have problem with cases so you should import that component in parent one as follows :
import TodoItem from './TodoItem'
and register it like :
export default{
....
components:{
TodoItem
}
....
}
for more details check this
a common mistake
dont forget import child component in parent component
import ChildComponent fromt './ChildComponent';
export default{
components:{
ChildComponent
}
}
I have data that I need to pass from one component1 to another component2.
I don't use vuex or router.
Components tree:
-Parent
--Component1
--Component2
From first component1 I make ajax request, retrieving info and pushing to data.
board: [1,2,3,4,5]
And I need access that retrieved data in component2
Can I do It without vuex or router ?
Thank you :)
You could emit an event to parent from component1 having as parameters the updated board and in the parent one receive that and pass it through props to component2
In component1 :
this.$emit("sendToComp2",this.board);
in the parent component :
<template>
<component1 #sendToComp2="sendTo2"/>
...
<component2 :boards="boards" />
....
</template>
data:{
boards:[]
},
methods:{
sendTo2(boards){
this.boards=boards
}
}
component2 should have property called boards
props:["boards"]
The idea is that you have a Parent component which has at least two child components. The child components can trigger an event in the parent component and from Parent to child. So, if Component1 needs to send a message to Component2, it can trigger an event to Parent and then Parent trigger an event for Component2. Example:
<script>
export default {
name: 'Car',
methods: {
handleClick: function() {
this.$emit('clickedSomething')
}
}
}
</script>
and
<template>
<div>
<Car v-on:clickedSomething="handleClickInParent" />
<!-- or -->
<Car #clickedSomething="handleClickInParent" />
</div>
</template>
<script>
export default {
name: 'App',
methods: {
handleClickInParent: function() {
//...
}
}
}
</script>
Source: https://flaviocopes.com/vue-components-communication/
You have to follow the "common ancestor pattern".
Consider the following Parent component:
<template>
<div>
<child-one :onData="onDataFromChildOne"></child-one>
<child-two :newData="dataToChildTwo"></child-two>
</div>
</template>
<script>
export default {
name: "Parent",
data() {
return {
dataToChildTwo: null
}
},
methods: {
onDataFromChildOne(data) {
this.dataToChildTwo = data;
}
}
}
</script>
The ChildOne component will receive a function as a prop named onData that should be called when the ajax call is finished. Then:
<script>
import axios from 'axios';
export default {
name: "ChildOne",
props: ['onData'],
beforeMount() {
axios
.get('/data')
.then(res => {
this.onData(res.data);
});
}
}
</script>
When onData gets executed, dataToChildTwo will be updated and ChildTwo will receive the new data.
Using single file architecture I'm trying to pass data (an object) from a parent component to a child:
App.vue
<template>
<div id="app">
<app-header app-content={{app_content}}></app-header>
</div>
</template>
<script>
import appHeader from './components/appHeader'
import {content} from './content/content.js'
export default {
components: {
appHeader
},
data: () => {
return {
app_content: content
}
}
}
</script>
appHeader.vue
<template>
<header id="header">
<h1>{{ app_content }}</h1>
</header>
</template>
<script>
export default {
data: () => {
return {
// nothing
}
},
props: ['app_content'],
created: () => {
console.log(app_content) // undefined
}
}
</script>
Seems to be such a trivial task and probably the solution is quite simple. Thanks for any advice :)
You're almost there.
In order to send the app_content variable from App.vue to the child component you have to pass it as an attribute in the template like so:
<app-header :app-content="app_content"></app-header>
Now, in order to get the :app-component property inside appHeader.vue you will have to rename your prop from app_component to appComponent (this is Vue's convention of passing properties).
Finally, to print it inside child's template just change to: {{ appContent }}