I'm fairly new to VueJS and I am having difficulties getting a child component to work properly.
So first off, I had some code in a "view" that I realized I wanted to use more than once, so I began to factor that code out into a separate component, but I ran into this problem:
[Vue warn]: Property or method "<feedbackCallback|stateCallback|submitCallback>" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
My setup is the following:
Vue 2.4.2
Webpack 3.5.5
Bootstrap-Vue 1.0.0
Vue-Router 2.7.0
I'm also using Single File Components
I'm going to call the file for the "view" ViewA and the file for the "component" "CompA"
ViewA with the parts removed that weren't going into a separate component:
<template>
[...]
<b-form #submit="submitCallback">
<b-form-group
id="groupID"
description=""
label="Set Thing Here" :feedback="feedbackCallback"
:state="stateCallback">
<b-form-input
id="inputID" :state="stateCallback"
v-model.trim="thing">
</b-form-input>
</b-form/group>
<b-button type="submit" variant="primary">Submit</b-button>
</b-form>
[...]
</template>
<script>
export default {
[...]
data () {
return {
thing: '',
[...]
}
},
computed: {
stateCallback () {
return 'invalid'
},
feedbackCallback () {
return 'Please enter a valid thing'
}
},
methods: {
submitCallback (event) {
[...]
}
},
[...]
</script>
Now, I moved these guys into CompA.
This is the code now where I'm getting the error:
ViewA:
<template>
[...]
<comp-a v-model.trim="thing" :thingvalue="thing"></comp-a>
[...]
</template>
<script>
import CompA from '../components/CompA'
export default {
name: 'view-a'
components: {
CompA
},
data () {
return {
thing: ''
}
}
}
</script>
CompA:
<template>
<b-form #submit="submitCallback">
<b-form-group
id="groupID"
description=""
label="Set Thing Here" :feedback="feedbackCallback"
:state="stateCallback">
<b-form-input
id="inputID" :state="stateCallback"
:value="thingvalue">
</b-form-input>
</b-form/group>
<b-button type="submit" variant="primary">Submit</b-button>
</b-form>
</template>
<script>
export default {
props: {
thingvalue: {
type: String
required: true
}
},
computed: {
stateCallback () {
return 'invalid'
},
feedbackCallback () {
return 'Please enter a valid thing'
}
},
methods: {
submitCallback (event) {
[...]
}
}
}
</script>
You might notice when I moved the code over, I changed the v-model.trim="thing" to :value="thing". I was getting the same error with thing until I did this.
Now is there something I'm missing? I've been digging a lot to try and understand. I even looked at some of bootstrap-vue's code to see if they do anything funky. But it turns out they have some computed properties being used in a very similar fashion. So I'm not understanding where the problem is happening. Let me know if you need more information.
Update
I'm more convinced that there is something going on with WebPack and VueJS as I'm not finding any definition of these properties/methods in the bundled up js file.
Well turns out it was simply a dumb mistake on my part. Did not have a closing script tag. Eslint wasn't catching either (maybe there is a setting to make sure it does), so it compiled with webpack just fine. Lesson: Always remember your closing tag!
Related
Why am I getting "$attrs is readonly" and "$listeners is readonly" errors in the child component of the external library every time something is updated in the parent component inside the Nuxt application?
This is how the whole bunch of errors looks like, caused by one problem:
[Vue warn]: $attrs is readonly.
found in
---> <MyComponent>
// ...
[Vue warn]: $listeners is readonly.
found in
---> <MyComponent>
// ...
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "data"
found in
---> <MyComponent>
// ...
About the mutation of the props. I don’t do it! How can I update props from MyComponent while in the parent component? MyComponent is a library that is imported into the parent component. It is not possible to update props inside MyComponent, I only pass props to MyComponent...
Sometimes the prop mutation error occurs only once during the initial page load.
I have an external library. Its essence is to provide access to its internal component. This is how main.ts looks like:
import MainComponent from './main.vue';
export default MainComponent;
The main.vue file is a regular vue component wrapped in Vue.extend for TypeScript.
I have reduced all the code as much as possible in order to make it as easy as possible to understand the problem:
<template>
<div>test</div>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: {
data: {
type: Object,
required: true,
},
},
data(): { sourceData: Record<string, any> } {
return {
sourceData: {},
};
},
watch: {
sourceData: {
handler(newData: Record<string, any>): void {
this.$emit('update', newData);
},
deep: true,
},
},
created(): void {
this.sourceData = { ...this.data };
},
});
</script>
Problems in this piece of code:
watch: {
sourceData: {
handler(newData: Record<string, any>): void {
this.$emit('update', newData);
},
deep: true,
},
},
On the Nuxt side of the application, it looks like this:
<MyComponent
#update="updateData($event)"
:data="sourceData"
/>
export default Vue.extend({
layout: 'profile',
data (): Record<string, any> {
return {
sourceData: {
// some source data
},
updatedSourceData: {},
}
},
methods: {
updateData(newSourceData: Record<string, any>): void {
this.updatedSourceData = { ...newSourceData }
}
},
// ...
In other words, I can say that this code is the problem:
this.updatedSourceData = { ...newSourceData }
I want to put new data in a separate object to render it (for example, in pre code). But this is what causes the error.
I have been trying to solve this problem for several hours, tried everything I could find and understand. Help me please.
UPD
I rewrote the component using a different approach. Also I gave up on building the library. Now I just import the component file from the package.
This is how the component code looks now:
<template>
<div>
{{ syncedData }}
</div>
</template>
<script lang="ts">
import { Component, PropSync, Vue } from 'vue-property-decorator';
#Component
export default class MyComponent extends Vue {
#PropSync('data', { type: Object }) syncedData!: Record<string, unknown>;
}
</script>
Inside the application, I use it like this:
<MyComponent
:data.sync="data"
/>
The problem is identical, it does not change throughout all my changes. Whatever I do with this component inside the package (library) - nothing helps.
And the craziest thing:
import MyComponent from '/Users/Colibri/projects/my-lib/src/components/my_component.vue'
That is, it's not even a library already, I just import it from another directory. I’m going crazy because I don’t understand why this is happening.
If I copy this component into a Nuxt project, then the errors disappear. The problem is somewhere here, but I absolutely do not understand it.
AS title sates, I don't so much need a solution but I don't understand why I'm getting the undesired result;
running v2 vue.js
I have a vue component in a single component file.
Basically the vue should render data (currently being imported from "excerciseModules" this is in JSON format).
IT's dynamic so based on the url path it determines what to pull out of the json and then load it in the page, but the rendering is being done prior to this, and I'm unsure why. I've created other views that conceptually do the samething and they work fine. I dont understand why this is different.
I chose the way so I didn't have to create a ton of routes but could handle the logic in one view component (this one below).
Quesiton is why is the data loading empty (it's loading using the empty "TrainingModules" on first load, and thereafter it loads "old" data.
Example url path is "https...../module1" = page loads empty
NEXT
url path is "https..../module 2" = page loads module 1
NEXT
url path is "https..../module 1" = page loads module 2
//My route
{
path: '/excercises/:type',
name: 'excercises',
props: {
},
component: () => import( /* webpackChunkName: "about" */ '../views/training/Excercises.vue')
}
<template>
<div class="relatedTraining">
<div class="white section">
<div class="row">
<div class="col s12 l3" v-for="(item, index) in trainingModules" :key="index">
<div class="card">
<div class="card-content">
<span class="card-title"> {{ item.title }}</span>
<p>{{ item.excercise }}</p>
</div>
<div class="card-action">
<router-link class="" to="/Grip">Start</router-link>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
console.log('script');
let trainingModules; //when initialized this is empty, but I would expect it to not be when the vue is rendered due to the beforeMount() in the component options. What gives?
/* eslint-disable */
let init = (params) => {
console.log('init');
console.log(trainingModules);
trainingModules = excerciseModules[params.type];
//return trainingModules
}
import { getRandom, randomImage } from '../../js/functions';
import { excerciseModules } from '../excercises/excercises_content.js'; //placeholder for JSON
export default {
name: 'excercises',
components: {
},
props: {
},
methods: {
getRandom,
randomImage,
init
},
data() {
return {
trainingModules,
}
},
beforeMount(){
console.log('before mount');
init(this.$route.params);
},
updated(){
console.log('updated');
},
mounted() {
console.log('mounted');
//console.log(trainingModules);
}
}
</script>
I can't tell you why your code is not working because it is an incomplete example but I can walk you through a minimal working example that does what you are trying to accomplish.
The first thing you want to do, is to ensure your vue-router is configured correctly.
export default new Router({
mode: "history",
routes: [
{
path: "/",
component: Hello
},
{
path: "/dynamic/:type",
component: DynamicParam,
props: true
}
]
});
Here I have a route configured that has a dynamic route matching with a parameter, often called a slug, with the name type. By using the : before the slug in the path, I tell vue-router that I want it to be a route parameter. I also set props: true because that enables the slug value to be provided to my DynamicParam component as a prop. This is very convenient.
My DynamicParam component looks like this:
<template>
<div>
<ul>
<li v-for="t in things" :key="t">{{ t }}</li>
</ul>
</div>
</template>
<script>
const collectionOfThings = {
a: ["a1", "a2", "a3"],
b: ["b1", "b2"],
c: [],
};
export default {
props: ["type"],
data() {
return {
things: [],
};
},
watch: {
type: {
handler(t) {
this.things = collectionOfThings[t];
},
immediate: true,
},
},
};
</script>
As you can see, I have a prop that matches the name of the slug available on this component. Whenever the 'slug' in the url changes, so will my prop. In order to react to those changes, I setup a watcher to call some bit of code. This is where you can make your fetch/axios/xhr call to get real data. But since you are temporarily loading data from a JSON file, I'm doing something similar to you here. I assign this data to a data value on the component whenever the watcher detects a change (or the first time because I have immediate: true set.
I created a codesandbox with a working demo of this: https://codesandbox.io/s/vue-routing-example-forked-zesye
PS: You'll find people are more receptive and eager to help when a minimal example question is created to isolate the problematic code. You can read more about that here: https://stackoverflow.com/help/minimal-reproducible-example
Description
Hello there,
I would like to share my Vue-components using bit.dev.
I got a Vue-component like this:
<template>
...
</template>
<script>
import CustomItem from "../../../../objects/CustomItem";
export default {
name: "Test",
props: {
item: {
type: CustomItem,
},
},
};
</script>
As you can see, this component requires the prop to be a specific object.
This is the CustomObject
export default class CustomItem {
constructor ({id, name}) {
this.id = id;
this.name = name;
}
// provide cool functions here
}
This works fine in my project, but not if I include this this way:
<template>
<div v-if="!$wait.is('item.loading')">
<MyComponent :item="item"/>
</div>
</template>
<script>
import MyComponent from '#bit/myproject.my-component'
import CustomItem from '#bit/myproject.custom-item';
export default {
name: 'Home',
components: {MyComponent},
data () {
return {
item: {}
};
},
beforeRouteEnter (to, _from, next) {
const promises = [
axios.get (`/api/item/1`)
];
next (vm => {
vm.$wait.start ('item.loading');
axios.all (promises)
.then (([itemRes]) => {
vm.item = new CustomItem(itemRes.data.data);
}).finally(()=>{
vm.$wait.end ('item.loading');
});
});
},
};
</script>
In this case I get this error:
[Vue warn]: Invalid prop: type check failed for prop "item". Expected T, got Object
found in
---> <MyComponent> at resources/js/components/Items/MyComponent.vue
What did I miss here?
Edit
As I can see, the component #bit/myproject.my-component which has been provided by bit.dev, provides a packed and minified version of my component. In there, the prop looks like this:
props:{item:{type:function t(e){var n=e.id,r=e.name})...
So I guess this is why this happens.
Basically it seems that in your project actually there are 2 classes CustomItem:
the one you imported relatively:
import CustomItem from "../../../../objects/CustomItem";
and the one you imported as an external package:
import CustomItem from '#bit/myproject.custom-item';
So I would try first to check if it works when you unify your imports to one form:
import CustomItem from '#bit/myproject.custom-item';
But is JS world things are not simple sometimes and even this may not help you - sometimes even referring to CustomItem in this way does not guarantee that there won't be more than one CustomItem in your production codebase. The solution I would suggest is to enforce 'duck-typing' in a custom props validator, if it's really important to check the type of the prop. You still cannot use JS instanceof as it won't work, even checking item.prototype.name === 'CustomItem' is not a good idea, as class names are changed during code minimalization, so duck-typing seems to be the only reasonable solution for you.
I have vue template data as string. For example,
String s = "<div>{{myData}}</div>"
And now I want to render in my already defined vue component.
<template>
<div>
HERE I NEED TO PLACE THE STRING
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data: {
myData: "IRONMAN"
},
}
</script>
Now I want the output as IRONMAN
How can i achieve this? Pleas help.
Thanks
You can have a single file component and do this - I have one called Dynamic.vue which accepts a HTML string - I use this to allow the users to generate their own templates and the bindings all match up properly, something like:
<script>
export default {
data () {
return {
someVar: 'Test'
}
},
props: {
templateHtml: {
templateHtml: true,
type: String
}
},
created () {
this.$options.template = this.templateHtml
}
}
</script>
If you were to call it like:
this.htmlData = '<div>Hello - {{{someVar}}</div>'
....
<my-dynamic-component :template-html="htmlData" />`
You would see the output
Hello Test
You would then omit the <template> part in the SFC.
Note: In order for this to work, you must also have the Vue Compiler included in your project (as this handles compiling the SFC into render functions which Vue uses to display data).
This link: https://v2.vuejs.org/v2/guide/installation.html#CLI can give more information about including the Vue Compiler.
Okay, so I've been bumping my head on the walls trying to figure out what the hell's going on here. See, I've been trying to load a module dynamically in Vue.js. What confuses me here is that the following actually works (i.e. when I hardcode the path to my module):
currentModal(){
return () => System.import(`../../ride/modals/boarding.vue`);
},
However, if I do this:
currentModal(){
let path = "../../ride/modals/";
return () => System.import(`${path}boarding.vue`);
},
I get the following error:
Cannot find module '../../ride/modals/boarding.vue'.
Then, if I go the other way around and do:
currentModal(){
let content = "boarding.vue";
return () => System.import(`../../ride/modals/${content}`);
},
The error becomes:
Error in render: "TypeError: undefined is not a function"
Obviously, something's different when I use a variable instead of hardcoding the path... but for the life of me, I can't figure out what. Anyone has any clue?
As Bergur mentioned in his comment, the problem is indeed that webpack cannot resolve a string that doesn't yet exist (issue explained here). What I did instead is add a property to my component that I called "content-list" and in the main page, I fill it with my desired components like so:
parent component:
<template>
.....
<modal-window
:init-visible="modalVisible"
:content-list="contentList"
#modalClosed="modalClosed">
</modal-window>
.....
</template>
<script>
import mainContent from './modals/main.vue'; //I doubt this is still useful
import otherContent from './modals/other.vue'; //I doubt this is still useful
export default{
data(){
return {
modalVisible: false,
contentList: {
main: () => System.import("./modals/main.vue"),
other: () => System.import("./modals/other.vue")
},
}
},
}
</script>
Inside the modal component:
<template>
...
<div ref="scrolling-pane" class="scrolling-pane" :class="{ 'scrolling-pane-normal': !isDirty, 'scrolling-pane-dirty': isDirty }" key="b">
<transition name="content-slide"
:enter-active-class="enterClassName"
:leave-active-class="leaveClassName"
#before-enter="beforeEnter"
#before-leave="beforeLeave"
#after-enter="afterEnter"
#after-leave="afterLeave">
<keep-alive>
<component :is="currentModal" #switchContent="onSwitchContent"></component>
</keep-alive>
</transition>
</div>
...
</template>
<script>
export default{
components: {
},
props: {
initVisible: Boolean,
contentList: Object
},
data(){
return {
currentContent: this.contentList['main']
}
}
...
</script>