Vue Recursive Component Not Rendering - javascript

I am building an application with vue.js and vue-cli where you can configure an order. The user should be able to choose whether they want to configure another order after each order or if they are done. To achieve this, I want to reuse my order component recursively:
Order.vue:
<template>
<div id="order">
<other> // just to show that I used other components here and how
<div>
<button class="CustomButton" v-on:click="finalizeOrder">
Finalize Order
</button>
<button class="CustomButton" v-on:click="configureAdditionalOrder">
Configure Additional Order
</button>
</div>
<order v-if="additionalOrder" #passOrderObject="catchOrderObjectFromRecursiveChild" #finalizeOrder="passFinalizeOrder" />
</div>
</template>
<script>
import Other from '#/components/Other.vue'
export default {
components: {
Other
},
data () {
return {
additionalOrder: false
}
},
methods: {
configureAdditionalOrder () {
this.buildOrderObject()
this.additionalOrder = true
},
catchOrderObjectFromRecursiveChild (order) {
this.$emit('passOrderObject', order)
},
passFinalizeOrder () {
this.$emit('finalizeOrder')
},
finalizeOrder () {
this.buildOrderObject()
this.$emit('finalizeOrder')
},
buildOrderObject () {
var order = {
name: "abc",
price: "1000"
}
this.$emit('passOrderObject', order)
}
}
</script>
<style>
</style>
App.vue:
<template>
<div id="app">
<h1>Ordering System</h1>
<order #passOrderObject="catchOrderObject" #finalizeOrder="finalizeOrder" />
</div>
</template>
<script>
import Vue from 'vue'
import Order from '#/components/Order.vue'
export default {
el: '#app',
components: {
Order
},
data () {
return {
finalization: false,
orderObjects: []
}
},
methods: {
finalizeOrder () {
this.finalization = true
},
catchOrderObject (order) {
this.orderObjects.push(order)
}
</script>
As you can see, I used a boolean variable in my component that should show the same component an additional time if the button "Configure Additional Order" is clicked. I use custom events to pass data to the parent component (App.vue), and the order component can handle these events from their recursive children by just passing them further.
The app itself works, but clicking the button to configure an additional order does nothing except emitting the custom event. What did I do wrong here?

For recursive component you should provide a name to that component :
<script>
import Other from '#/components/Other.vue'
export default {
name:'order',// this is the component name
components: {
Other
},
data () {
return {
additionalOrder: false
}
},
...

Related

Passing vue.js Route Params to Component

I'm having trouble getting a route param to pass directly into a component. I followed multiple sets of directions in the docs (including using the Composition API as in the following code), but I'm still getting undefined when the CourseModule.vue first renders.
Route Definition
{
path: '/module/:id',
name: 'Course Module',
props: true,
component: () => import('../views/CourseModule.vue'),
},
CourseModule.vue:
<template>
<div class="AppHome">
<CustomerItem />
<CourseModuleItem :coursemodule-id="this.CoursemoduleId"/>
</div>
</template>
<script>
import { useRoute } from 'vue-router';
import CustomerItem from '../components/customers/customer-item.vue';
import CourseModuleItem from '../components/coursemodules/coursemodule-item.vue';
export default {
setup() {
const route = useRoute();
alert(`CourseModule.vue setup: ${route.params.id}`);
return {
CoursemoduleId: route.params.id,
};
},
components: {
CustomerItem,
CourseModuleItem,
},
mounted() {
alert(`CourseModule.vue mounted: ${this.CoursemoduleId}`);
},
};
</script>
coursemodule-item.vue:
<template>
<div id="module">
<div v-if="module.data">
<h2>Course: {{module.data.ModuleName}}</h2>
</div>
<div v-else-if="module.error" class="alert alert-danger">
{{module.error}}
</div>
<Loader v-else-if="module.loading" />
</div>
</template>
<script>
import Loader from '../APILoader.vue';
export default {
props: {
CoursemoduleId: String,
},
components: {
Loader,
},
computed: {
module() {
return this.$store.getters.getModuleById(this.CoursemoduleId);
},
},
mounted() {
alert(`coursemodule-item.vue: ${this.CoursemoduleId}`);
this.$store.dispatch('setModule', this.CoursemoduleId);
},
};
</script>
The output from my alerts are as follows:
CourseModule.vue setup: zzyClJDQ3QAKuQ2R52AC35k3Hc0yIgft
coursemodule-item.vue: undefined
CourseModule.vue mounted: zzyClJDQ3QAKuQ2R52AC35k3Hc0yIgft
As you can see, the path parameter works fine in the top level Vue, but not it's still not getting passed into the component.
your kebab-cased :coursemodule-id props that you're passing to the CourseModuleItem component becomes a camelCased coursemoduleId props
Prop Casing (camelCase vs kebab-case)
try this
// coursemodule-item.vue
...
props: {
coursemoduleId: String,
},
...
mounted() {
alert(`coursemodule-item.vue: ${this.coursemoduleId}`);
this.$store.dispatch('setModule', this.coursemoduleId);
},

Reactive property is not propagating to component in Vue 3 app

I have a Vue 3 app. I am trying to setup a store for state management. In this app, I have the following files:
app.vue
component.vue
main.js
store.js
These files include the following:
store.js
import { reactive } from 'vue';
const myStore = reactive({
selectedItem: null
});
export default myStore;
main.js
import { createApp } from 'vue';
import App from './app.vue';
import myStore from './store';
const myApp = createApp(App);
myApp.config.globalProperties.$store = myStore;
myApp.mount('#app');
component.vue
<template>
<div>
<div v-if="item">You have selected an item</div>
<div v-else>Please select an item</div>
<button class="btn btn-primary" #click="generateItem">Generate Item</button>
</div>
</template>
<script>
export default {
props: {
item: Object
},
watch: {
item: function(newValue, oldValue) {
alert('The item was updated.');
}
},
methods: {
generateItem() {
const item = {
id:0,
name: 'Some random name'
};
this.$emit('itemSelected', item);
}
}
}
</script>
app.vue
<template>
<component :item="selectedItem" #item-selected="onItemSelected" />
</template>
<script>
import Component form './component.vue';
export default {
components: {
'component': Component
},
data() {
return {
...this.$store
}
},
methods: {
onItemSelected(item) {
console.log('onItemSelected: ');
console.log(item);
this.$store.selectedItem = item;
}
}
}
</script>
The idea is that the app manages state via a reactive object. The object is passed into the component via a property. The component can then update the value of the object when a user clicks the "Generate Item" button.
I can see that the selectedValue is successfully passed down as a property. I have confirmed this by manually setting selectedValue to a dummy value to test. I can also see that the onItemSelected event handler works as expected. This means that events are successfully flowing up. However, when the selectedItem is updated in the event handler, the updated value is not getting passed back down to the component.
What am I doing wrong?
$store.selectedItem stops being reactive here, because it's read once in data:
data() {
return {
...this.$store
}
}
In order for it to stay reactive, it should be either converted to a ref:
data() {
return {
selectedItem: toRef(this.$store, 'selectedItem')
}
}
Or be a computed:
computed: {
selectedItem() {
return this.$store.selectedItem
}
}

Mixin for destroyed Vue component is still listening for events

I have a parent component that conditionally renders one of two child components:
<template>
<div>
<!-- other code that changes conditional rendering -->
<folders v-if="isSearchingInFolders" :key="1234"></folders>
<snippets v-if="!isSearchingInFolders" :key="5678"></snippets>
</div>
</template>
Each of these components use the same mixin (searchMixin) locally like so:
<template>
<div>
<div>
<snippet
v-for="item in items"
:snippet="item"
:key="item.id">
</snippet>
<img v-if="busy" src="/icons/loader-grey.svg" width="50">
</div>
<button #click="getItems">Get More</button>
</div>
</template>
<script>
import searchMixin from './mixins/searchMixin';
import Snippet from './snippet';
export default {
components: { Snippet },
mixins: [searchMixin],
data() {
return {
resourceName: 'snippets'
}
},
}
</script>
Each of the components is functionally equivalent with some slightly different markup, so for the purposes of this example Folders can be substituted with Snippets and vice versa.
The mixin I am using looks like this (simplified):
import axios from 'axios'
import { EventBus } from '../event-bus';
export default {
data() {
return {
hasMoreItems: true,
busy: false,
items: []
}
},
created() {
EventBus.$on('search', this.getItems)
this.getItems();
},
destroyed() {
this.$store.commit('resetSearchParams')
},
computed: {
endpoint() {
return `/${this.resourceName}/search`
},
busyOrMaximum() {
return this.busy || !this.hasMoreItems;
}
},
methods: {
getItems(reset = false) {
<!-- get the items and add them to this.items -->
}
}
}
In the parent component when I toggle the rendering by changing the isSearchingInFolders variable the expected component is destroyed and removed from the DOM (I have checked this by logging from the destroyed() lifecycle hook. However the searchMixin that was included in that component does not appear to be destroyed and still appears to listen for events. This means that when the EventBus.$on('search', this.getItems) line is triggered after changing which component is actively rendered from the parent, this.getItems() is triggered twice. Once for folders and once for snippets!
I was expecting the mixins for components to be destroyed along with the components themselves. Have I misunderstood how component destruction works?
Yes, when you pass an event handler as you do EventBus keeps the reference to the function you passed into. That prevents the destruction of the component object. So you need clear the reference by calling EventBus.$off so that the component can be destructed. So your destroy event hook should look like this:
destroyed() {
this.$store.commit('resetSearchParams')
EventBus.$off('search', this.getItems)
},

vueJS - Using 'this' in a callback function

EDIT --- SOLVED
It turns out that isn't really a problem, Vue will auto-bind for you so there's no need to bind manually.
END EDIT ---
I'm trying to pass a method to a callback(or event) to a child component.
Everything works great, except that the function executes in the wrong context.
In react, I would bind the functions in the constructor, I'm not sure what's the solution is in Vue.
Example
<template>
<div id="app">
<Header/>
<Tasks
:todos="todos"
#onMarkAsDone="markAsDone"
>
</Tasks>
</div>
</template>
<script>
import Header from './components/Header.vue';
import Tasks from './components/Tasks.vue';
export default {
name: 'my-component',
data() {
return {
name: 'Tasks',
todos: [{
id:0,
text:'Complete this by lunch',
isDone: false
}]
}
},
methods: {
markAsDone(id) {
console.log(this); // refers to the child component
// can't access this.todos
}
},
components: {
Tasks,
Header
}
}
</script>
Here's the solution to it, turns out you can use the 'created' life cycle hook, this is similar to react when binding in a constructor
<template>
<div id="app">
<Header/>
<Tasks
:todos="todos"
#onMarkAsDone="markAsDone"
>
</Tasks>
</div>
</template>
<script>
import Header from './components/Header.vue';
import Tasks from './components/Tasks.vue';
export default {
name: 'my-component',
data() {
return {
name: 'Tasks',
todos: [{
id:0,
text:'Complete this by lunch',
isDone: false
}]
}
},
methods: {
markAsDone(id) {
console.log(this.todos); // can now access the correct 'this'
}
},
created() {
this.markAsDone = this.markAsDone.bind(this);
},
components: {
Tasks,
Header
}
}
</script>
Sub component code
<template>
<ul>
<li
:class="{isDone:todo.isDone}"
:key="todo.id"
v-for="todo in todos">
<input type='checkbox' #change="markAsDone(todo.id)"/>
{{todo.text}}
</li>
</ul>
</template>
<script>
export default {
name: 'Tasks',
props: ['todos'],
methods: {
markAsDone(id) {
this.$emit('onMarkAsDone', id);
}
}
}
</script>
You can return function in a markAsDone method, like this:
markAsDone() {
return id => {
console.log(this.todos);
}
},
and then when passing method as a prop call it:
:onMarkAsDone="markAsDone()"
Then you can call the prop method inside your Child-Component.
That should give you requested functionality without using bind in created() hook.

how to export tags and computed functions to main App.vue from components in VueCLI

I'm at the very start with VUE CLI 3, and was wondering if I could make clean my code importing from the components to the main App.vue such things like tags inside the template of those components, or even computed functions of that components, here on detail my problem:
This is one of my components (header), and I want to export that input tag and its computed function to the main App.vue without precisely write all that template code and scripts in there.
<template>
<div>
<input type='text' v-model='search' placeholder='Search for Tittle or Author'/>
</div>
</template>
<script>
export default {
name:"header",
props:{
header:Object
},
computed: {
filteredTitles:function()
{
return this.bookLists.filter((data)=>{return data.title.toUpperCase().includes(this.search.toUpperCase())})
}
}
}
</script>
This is my App.Vue file already with that component imported, but the point is even the element already was imported , the input doesn't show up, unless I write its code inside the App.vue template tag in fact.
<template>
<div id="app">
<div class="card-flipping">
<books v-for="(books,index) in bookLists" v-bind:key="index" v-bind:books="books" />
</div>
</div>
</template>
<script>
import books from "./components/books.vue";
import header from './components/header.vue';
export default {
name: "app",
components: {
header,
books,
},
data() {
return {
bookLists: []
};
},
methods: {
getJsonData() {
fetch(" https://api.myjson.com/bins/zyv02 ", {
method: "GET"
})
.then(response => {
return response.json();
})
.then(bookStore => {
this.bookLists = bookStore.books;
})
.catch(error => {
console.log(error);
});
}
},
computed: {
filteredTitles:function()
{
return this.bookLists.filter((data)=>{return data.title.toUpperCase().includes(this.search.toUpperCase())})
}
},
created() {
this.getJsonData();
}
};
</script>
Could somebody help me please?

Categories

Resources