been banging my head once more.
I'm surprised I haven't seen a similar question here on SOF, but it's really weird.
This code WAS working, but I moved into a standalone component to be used on multiple pages.
This is the error I'm getting when I go to the page:
handler.call is not a function
I know for a fact it is this component because if I remove the component from the page, there is no error and runs fine. The component calls no functions and has no functions in the script. I have no idea what is going on.
and in the console log, there isn't much help either:
TypeError: "handler.call is not a function"
NuxtJS 21
invokeWithErrorHandling
callHook
insert
invokeInsertHook
patch
_update
updateComponent
get
Watcher
mountComponent
$mount
mount
_callee5$
tryCatch
invoke
method
asyncGeneratorStep
_next
run
notify
flush
This is the very simple component source code:
<template>
<div>
<button v-if="can_edit" class='btn-blue'> Edit </button>
<div v-for="card in cards" class='my-credit-card' v-bind:key="card.id">
<h5>{{card.name}}</h5>
<h5 class='mt-0'>•••• •••• •••• {{card.last4}}</h5>
<p class='small'>Exp. {{card.expiration}}</p>
</div>
<div v-if="cards.length == 0">
<p class='subtle'>
Your saved payment methods will display here once you send your first card!
</p>
</div>
<a v-if="(can_add && cards.length > 0)" href="/add-card" class='action'> + Add New Card </a>
</div>
</template>
<script>
export default {
data : function(){
return {
cards : [
{
id: 1,
name: "Lisa Smith",
last4: "4231",
expiration: "12/2022"
},
{
id: 2,
name: "John Smith",
last4: "1234",
expiration: "11/2023"
},
],
};
},
props : {
can_add : {
default : true,
type: Boolean,
},
can_edit : {
default : true,
type: Boolean,
},
},
mounted : {
// fetch cards
},
}
</script>
and this is how I'm importing the component:
<template>
<section class='container'>
<h1>My Credit Cards</h1>
<mycards :can_add="true" :can_edit="true"></mycards>
</section>
</template>
<script>
import mycards from '~/components/my_cards.vue';
export default {
data : function(){
return {
test : 1,
};
},
components : {
mycards,
},
}
</script>
This:
mounted : {
// fetch cards
},
Should be:
mounted () {
// fetch cards
},
You're setting your mounted hook to an object, which won't have a call method on it.
Related
Hi I am trying to implement a treeview. I have a component for it which is hierarchical. The problem I am facing is that emit is not working inside tree component. Please help me out. here is my code...
TreeItem.vue
<template>
<div class="node" :style="{ marginLeft: depth * 20 + 'px' }">
<span class="menu-item" v-if="type == 'country'">
<button class="menu-button menu-button--orange" #click="addState()">
Add State
</button>
</span>
<TreeItem
v-if="expanded"
v-for="child in node.children"
:key="child.name"
:node="child"
:depth="depth + 1"
/>
</div></template>
<script>
export default {
name: "TreeItem",
props: {
node: Object,
depth: {
type: Number,
default: 0,
},
emits: ["add-state"],
},
data() {
return {
expanded: false,
};
},
methods: {
addState() {
console.log("Adding State"); //Prints this message to console
this.$emit("add-state");
},
},
};
</script>
AddressManager.vue
Inside template:
<div>
<tree-item :node="treedata" #add-state="addState"></tree-item>
</div>
Inside Methods:
addState() {
console.log("State will be added"); //Never prints this message
},
We need to emit on button click. But the emit is not working.
So I have figured out the problem... We need to replace this.$emit() with this.$parent.$emit()
Here is the change-
addState() {
console.log("Adding State"); //Prints this message to console
this.$parent.$emit("add-state");
},
Hopefully this resolved my issue.
You can use v-on="$listeners" to pass emits on levels above within the depths https://v2.vuejs.org/v2/guide/components-custom-events.html#Binding-Native-Events-to-Components it would just forward them to the parent.
However in nested scenarios it is more convenient using store (vuex) and assign a property then watch property changes from wherever you would like to notice its change. If you use vuex: https://vuex.vuejs.org/api/#watch store properties can be watched.
A WAY TO ACCESS DATA FROM ORIGINAL/BASE COMPONENT IN EXTENDED COMPONENT DATA()
A WAY TO MERGE IN VUE 3 LIKE VUE 2
If are you looking for a way to extends/mixins your code to add some functionality to your component(tag) organizing then, codding less and creating a beautiful clean legible vue structure but maybe you need to know the differences between vue 2 and 3 and helping-us to find some these anwers
EXEMPLE CODE
(Ex: Caller)
<!-- APP.VUE -->
<template>
<div id="app">
<h2>Original/base 'tag': <original message="Test String" /></h2>
<hr>
<h2>EXTENDED 'tag': <EXTENDoriginal message="Have this too" extra="THIS OTHER PROP" /></h2>
</div>
</template>
<script>
import BaseComponent from "./components/BaseComponent.vue"
import ExtendedComponent from "./components/ExtendedComponent.vue"
export default {
components: {
original: BaseComponent,
EXTENDoriginal: ExtendedComponent,
}
}
</script>
(Ex: Original/Base component)
<!-- BaseComponent.vue = Option API Component -->
<template>
<div style="color:red;">
<div>Original component: {{ message }}</div>
<div>{{ user }}</div>
</div>
</template>
<script>
export default {
name: "original",
props: {
message: {
type: String, required: false
}
},
data() {
return {
primitive: 1,
user: {
name: 'Jack', id: 1111
}
}
},
methods: {
ORIGINAL_FUNCTION(val) {
alert(val)
}
}
}
</script>
(Ex: Extended component from Original/Base)
<!-- ExtendedComponent.vue = Option API Component EXTENDED -->
<template>
<div style="color:blue;">
<div>EXTENDED component: <u>{{ message }}</u></div>
<div>plus <u>{{ extra }}</u></div>
<div>user: {{ user }}</div>
<h4 #click="exampleFuncExtended">Click here to call "original" function from "local" function</h4>
</div>
</template>
<script>
import Original from "./BaseComponent.vue"
export default {
name: "EXTENDoriginal",
extends: Original,
props: {
extra: {
type: String, required: false
}
},
data() {
let primitiveExtended = this.primitive + 99
return {
primitiveExtended,
user: {
id: 9999
}
}
},
methods: {
exampleFuncExtended() {
alert(this.primitive + '(ok) -- ' + this.primitiveExtended + ' (does not work)')
this.ORIGINAL_FUNCTION("hello it's me you're looking for - A call to original Sample")
}
}
}
</script>
MUST KNOW
VUE 2.5.2 will return ( try/test https://codesandbox.io/s/vue2-extends-example-q5rcpk )
Original/base 'tag':
Original component: Test String
{ "name": "Jack", "id": 1111 }
------------------------------------
EXTENDED 'tag':
EXTENDED component: Have this too
plus THIS OTHER PROP and NaN
user: { "id": 9999, "name": "Jack" } <------ VUE 2 MERGE
VUE 3.0.2 will return ( try/test https://codesandbox.io/s/vue3-extends-example-o50lh1 )
Original/base 'tag':
Original component: Test String
{ "name": "Jack", "id": 1111 }
------------------------------------
EXTENDED 'tag':
EXTENDED component: Have this too
plus THIS OTHER PROP and NaN
user: { "id": 9999 } <------ VUE 3 MERGE
1 - you can find some information about this at https://v3-migration.vuejs.org/breaking-changes/data-option.html and a mention "Migration build flag: OPTIONS_DATA_FN" but I'm looking for a way to merge like VUE 2 just into one simple component/page not all project
2 - the "NaN" it's a start to ask: Is there a way to read data from original/base component into data() section of extended component? Because they are accessible in and methods: like example code above do
I'm creating a Vue app to read comic books via Reveal.js. The component takes the data from the parent. there is an Axios call in the parent to provide the data from a rest API. I'm also using Vue router with the createWebHashHistory setup as I'm using a Django backend to provide the API.
If I refresh the page it will load the presentation correctly but when I navigate to the page it doesn't seem to initialise Reveal. there are no errors in the console.
I've tried to watch the route changing and other events to run Reveals sync or initialise but I've not had any success.
component
<template>
<div class="reveal" id="comic_box" ref="comic_box">
<div id="slides_div" class="slides">
<section v-for="(page, index) in comic_data.pages" :key="page.index" :data-menu-title="page.page_file_name">
<img :data-src="'/image/' + comic_data.selector + '/' + page.index " class="w-100" :alt="page.page_file_name">
</section>
</div>
</div>
</template>
<script>
import Reveal from "reveal.js";
export default {
name: "TheComicReader",
data () {
return {
}
},
props: {
comic_data: Object
},
methods: {
},
watch: {
'$route' (to, from) {
Reveal.initialize()
}
},
mounted () {
Reveal.initialize()
},
}
</script>
<style scoped>
</style>
comic_data
{
"selector": "e1b76b93-814c-4ee8-9104-8c8187977836",
"title": "Batman 125 (2022) (digital-SD).cbr",
"last_read_page": 0,
"pages": [
{
"index": 0,
"page_file_name": "Batman 125-000.jpg",
"content_type": "image/jpeg"
},
{
"index": 1,
"page_file_name": "Batman 125-001.jpg",
"content_type": "image/jpeg"
}
]
}
After further investigation I noticed that the DOM elements in Reveal were not updating after moving away from the page. I solved this by forcing Reveal to bind to the new comic_box by ref. This is now consistently loading the presentation correctly.
mounted () {
Reveal(this.$refs.comic_box).initialize()
}
How do I access a child component data from the parent component?
I have a parent component "MissionPlanner" which i want to access the child component "ChosenHeroes" array called "chosenHeroes". I want to ultimately render a div if an element is in that array - and be able to update the array in the parent
I am not sure if i should be using emit() and how exactly to use it. I tried making a custom event "addHero" and pass that back. But i am getting errors
ChosenHeroes.vue
<template>
<div>
<select v-model="chosenHero">
<!-- placeholder value -->
<option :value="null">Select a hero</option>
<!-- available heroes -->
<option v-for="hero in heroes"
:key="hero.name"
:value="hero.name">
{{ hero.name }}
</option>
</select>
<span> </span>
<button #click="addHero(chosenHero)"
:disabled="chosenHero === null || chosenHeroes.length >= 3">Add Hero</button>
<br>
<h3>Chosen Heroes</h3>
<div class="chosen-heroes">
<div v-for="(hero, i) in chosenHeroes"
:key="hero.name">
<strong>Slot {{ i + 1 }}:</strong>
<Hero :hero="hero"
#removeHero="removeHero(hero)" />
</div>
</div>
</div>
</template>
<script>
import Hero from "./Hero";
export default {
components: {
Hero
},
props: {
"heroes": Array
},
data() {
return {
chosenHero: null,
chosenHeroes: []
};
},
methods: {
addHero(name) {
if(this.chosenHeroes.length < 3) {
this.chosenHeroes.push({ name });
this.chosenHero = null;
}
this.$emit("add-hero",this.chosenHeroes);
},
removeHero(hero) {
this.chosenHeroes = this.chosenHeroes.filter(h => h.name != hero.name);
}
}
};
</script>
HeroPlanner.vue
<template>
<div>
<!-- justice leage application begins here -->
<h1 id="jl">Justice League Mission Planner</h1>
<ul class="roster">
<h3>Roster:</h3>
<li v-for="hero in heroes"
:key="hero.name">
<!-- to do: conditionally display this span -->
<span v-if="isInList(hero.name)">✔ </span>
<span>{{ hero.name }} </span>
<span class="edit"
#click="editHero(hero)">edit</span>
</li>
<br>
<input type="text"
placeholder="new name"
v-model="newName"
v-if="isEdit"
#keyup.enter="changeName"
#blur="clear">
<br>
<span v-if="isEdit">enter to submit, click outside the box to cancel</span>
</ul>
<chosen-heroes :heroes="heroes" :chosenHeroes="chosenHeroes" #add-hero="addHero" />
</div>
</template>
<script>
import ChosenHeroes from "./components/ChosenHeroes.vue";
export default {
components: {
"chosen-heroes" : ChosenHeroes
},
data() {
return {
heroes: [
{ name: "Superman" },
{ name: "Batman" },
{ name: "Aquaman" },
{ name: "Wonder Woman" },
{ name: "Green Lantern" },
{ name: "Martian Manhunter" },
{ name: "Flash" }
],
newName: "",
isEdit: false,
heroToModify: null,
chosenHeroes: ChosenHeroes.data
};
},
methods: {
...isInList(heroName) {
return this.chosenHeroes.map(heroObject => heroObject.name).includes(heroName);
}
And here are the errors I got when i ran it:
vue.runtime.esm.js?2b0e:619 [Vue warn]: Error in render: "TypeError: this.chosenHeroes.map is not a function"
found in
---> <MissionPlanner> at src/MissionPlanner.vue
<App> at src/App.vue
<Root>
warn # vue.runtime.esm.js?2b0e:619
vue.runtime.esm.js?2b0e:1888 TypeError: this.chosenHeroes.map is not a function
at VueComponent.isInList (webpack-internal:///./node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/MissionPlanner.vue?vue&type=script&lang=js&:78)
at eval (eval at ./node_modules/cache-loader/dist/cjs.js?{"cacheDirectory":"node_modules/.cache/vue-loader","cacheIdentifier":"aeb9565a-vue-loader-template"}!./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/MissionPlanner.vue?vue&type=template&id=e2c8c042&scoped=true& (app.js:946), <anonymous>:21:19)
at Proxy.renderList (vue.runtime.esm.js?2b0e:2630)
at Proxy.render (eval at ./node_modules/cache-loader/dist/cjs.js?{"cacheDirectory":"node_modules/.cache/vue-loader","cacheIdentifier":"aeb9565a-vue-loader-template"}!./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/MissionPlanner.vue?vue&type=template&id=e2c8c042&scoped=true& (app.js:946), <anonymous>:19:15)
at VueComponent.Vue._render (vue.runtime.esm.js?2b0e:3548)
at VueComponent.updateComponent (vue.runtime.esm.js?2b0e:4066)
at Watcher.get (vue.runtime.esm.js?2b0e:4479)
at new Watcher (vue.runtime.esm.js?2b0e:4468)
at mountComponent (vue.runtime.esm.js?2b0e:4073)
at VueComponent.Vue.$mount (vue.runtime.esm.js?2b0e:8415)
I went through this article here on the emit() and how to emit data from child components to the parent components but I am not sure I used it properly
Change #add-hero="addHero" to #add-hero="anyMethodName"
and create a method:
anyMethodName(value) {
//do what you want with the chosenHeroes
}
value is the chosenHeroes that was passed through from the child component.
See link for example: https://forum.vuejs.org/t/passing-data-back-to-parent/1201/2
If you want to pass data from child to parent you can pass a parent's method as a prop to the child:
PARENT TEMPLATE SECTION
<child-component :dataHandler="dataHandler">
</child-component>
PARENT METHOD IN SCRIPT METHODS SECTION
dataHandler (input) {
// handle your new data in parent component
}
CHILD SCRIPT PROPS SECTION
props: ["dataHandler"]
register your prop. You can use dataHandler in child as normal method and pass there new data as argument - the method will be executed in parent, but with the data you provided as argument in the child.
The error that you are getting suggests that chosenHeroes is not an array (maybe it's undefined?).
The $emit will work when it is called, and in parent, it will be same as events (firing only when events are happened). In this case, you need the data from the child always render a div in the parent (if what I understand is correct).
It is better to use the Vuex store for your purpose. You can sync the data into the store from the child component. Since the store data is global, it is accessible from all components.
I am using Laravel and vue-router.
<template>
<div class="content__inner">
<div class="forums">
<!-- Heading -->
<div class="forums__heading" :style="'border-bottom:2px solid #' + board.category.color">
<div class="lg-8 md-8 sm-12 column column__first">
<h2 class="forums__heading__title">{{ board.title }}</h2>
</div>
<div class="lg-1 md-1 sm-1 dtop column text-center">
<strong>Replies</strong>
</div>
<div class="lg-3 md-3 sm-4 column text-right">
<strong>Latest Reply</strong>
</div>
<div class="clearfix"></div>
</div>
<!-- Content -->
<div class="forums__content">
{{ board.category }}
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
board: [],
}
},
created() {
this.fetch_board(this.$route.params.slug);
},
methods: {
/**
* Fetch the board.
*
* #param string slug The slug for the board.
*/
fetch_board(slug)
{
this.$http.get('/api/forums/board/' + slug).then((response) => {
this.board = response.data;
});
},
}
};
</script>
The 'fetch_board' function returns an object like the following:
board:Object {
id:5,
title:"Game Discussion",
slug:"5-game-discussion",
description:"General talk about the game.",
restriction:null,
category_id:2,
category:Object {
id:2
title:"Community",
color:"2ECC71",
created_at:"2017-05-02 07:30:25",
updated_at:"2017-05-02 07:30:25",
}
created_at:"2017-05-02 07:30:25",
updated_at:"2017-05-02 07:30:25",
}
When I access the {{ board.category }} it displays the object correctly; but when I access {{ board.category.title }} it displays the title, but ALSO gives a TypeError.
Why I am getting this error if the data is being loaded correctly?
How can I avoid/fix this error?
You are seeing this error because you are initializing "board" to an empty array. The component tries to evaluate "board.category.title" when it binds the reactivity just prior to the created() hook.
With board set as an empty array, step by step the evaluation might look like this:
const board = [];
const category = board.category; // undefined
const title = category.title; // TypeError, because category is undefined
You should stop seeing this error if you initialize your data like so:
data() {
return {
board: {
category: {
title: ''
}
}
}
}
Here is the Vue lifecycle diagram which illustrates when the created() event is fired
This error is explained in the official Vue documentation:
Since Vue doesn’t allow dynamically adding root-level reactive properties, you have to initialize Vue instances by declaring all root-level reactive data properties upfront, even with an empty value:
var vm = new Vue({
data: {
// declare message with an empty value
message: ''
},
template: '<div>{{ message }}</div>'
})
// set `message` later
vm.message = 'Hello!'
If you don’t declare message in the data option, Vue will warn you that the render function is trying to access a property that doesn’t exist.