[Vue warn]: Failed to mount component when using mixin with a parameter - javascript

since yesterday I'm struggling with creating a Vue mixin with a parameter and I'm getting a [Vue warn]: Failed to mount component: template or render function not defined.
Here is my JS file including mixin:
export default (dataObject) => ({
data() {
return {
inputValue: ''
}
},
methods: {
updateValue(newValue) {
this.inputValue = newValue
}
},
mounted() {
this.$bus.$on('value-changed', this.updateValue)
},
beforeDestroy() {
this.$bus.$off('value-changed');
},
computed: {
filteredData() {
if (this.inputValue !== '') {
let newData = Object.keys(dataObject)
.filter(key => key.includes(this.inputValue))
.reduce(
(newData, current) => ((newData[current] = dataObject[current]), newData), {}
)
return newData
}
else return dataObject
}
}
})
And here is my Vue component:
import searchLogic from '../logic/searchLogic.js'
import { mapState } from 'vuex'
export default {
computed: {
...mapState(['champions']),
},
mixins: [searchLogic(this.champions)]
}
Importing this file works, because when I try to import a normal mixin without arguments it works properly. I also tried passing champions and "champions" instead of this.champions but none seem to work. Is it some problem with a mixin? I read that it's possible to return a function returning an object to use parameters when creating mixins.

Based on this article you should be able to statically pass a parameter, but not dynamically.
https://forum.vuejs.org/t/pass-parameters-to-a-mixin-method/26401/3
However, what I can see from the warn you get and the code you shared, is that you haven't defined any template for the component using the template property or the template element in the .vue file.

Related

Vue use computed property in data

I have the following const properties in my Vue data.
<script>
import { mapGetters, mapActions } from 'vuex'
export default {
data() {
const properties = ['Property 1', 'Property 2']
return {
properties,
form: propertyNames.map(name => ({ property_type: [] })),
}
},
I however am computing the properties in a computed property as follows
computed: {
properties() {
const properties = this.property_form.property_name;
const filtered = properties.filter(p => p != null)
return filtered;
}
},
so I want to use the computed property in the data as follows:
<script>
import { mapGetters, mapActions } from 'vuex'
export default {
data() {
const properties = this.properties
return {
properties,
form: propertyNames.map(name => ({ property_type: [] })),
}
},
but when I do this, I get the error Property or method "properties" is not defined on the instance.
data() is invoked before computed props are resolved, so you can't reference computed props from there.
I assume this question is related to your other question, where form needs to be used in v-model. Since form depends on the computed prop, you should use a watcher on the computed prop that updates form accordingly:
export default {
data() {
return {
form: []
}
},
computed: {
properties() {/*...*/},
},
watch: {
properties(properties) {
this.form = properties.map(_ => ({ property_type: [] }))
}
}
}
What is const properties = ['Property 1', 'Property 2'] ?
Pretty sure you cannot have it nested inside of data. Move it to the upper scope maybe or define it directly like
data() {
return {
properties: ['Property 1', 'Property 2']
}
}
Also, it's not very clear as of what you're trying to achieve here. A lot of things are mixed together. You're not really supposed to make a map in data since it's here for raw data (as it's called).
Here is the official documentation on computed properties, maybe give it a second read or update your question by explaining a bit better what is happening here.

problem with using vue.js routing and mixin

Vue developers.
Now I'm trying vuebnb tutorial and have a problem with routing and mixins.
I tried to assign data before entering the router using beforeRouteEnter guard, but it seems like my template is rendered before data assign.
Following is the code I tried.
ListingPage.vue
<template>
<div>
<img :src="'/' + images[1].img_path" />
</div>
</template>
<script>
import { populateAmenitiesAndPrices } from "../js/helpers";
import routeMixin from '../js/route-mixin';
export default {
mixins: [ routeMixin ],
data() {
return {
title: null,
about: null,
address: null,
amenities: [],
prices: [],
images:[],
}
},
methods: {
assignData({ listing, images }) {
console.log('inside_component_before_assign');
this.images = [...images];
Object.assign(this.$data, populateAmenitiesAndPrices(listing));
console.log('inside_component_after_assign');
}
},
components: {
}
};
</script>
route-mixin.js
import axios from 'axios';
function getData(to) {
return new Promise((resolve) => {
let serverData = JSON.parse(window.vuebnb_data);
if (!serverData.path || to.path !== serverData.path) {
axios.get(`/api${to.path}`).then(({ data }) => {
resolve(data);
});
} else {
resolve(serverData);
}
});
}
export default {
beforeRouteEnter: (to, from, next) => {
console.log('before_next');
getData(to).then((data) => {
next(component => {
component.assignData(data);
});
});
console.log('after_next');
}
};
Here data is a object and it is like {listing: {...}, images: Array(5), path: "/listing/1}.
Data fetch from the server is checked.
But when I try to render ListingPage.vue, error is logged in console like this:
*
[Vue warn]: Error in render: "TypeError: Cannot read property 'img_path' of undefined"
found in
---> <ListingPage> at resources/assets/components/ListingPage.vue
<App> at resources/assets/components/App.vue
<Root>
Regardless of the error, the page is displayed successfully. Please help me to get rid of this error:
From the reply on your comment I conclude your problem is solved by adding a v-if to your html tag?
Changing your code to
<img v-if="images[1]" :src="'/' + images[1].img_path" />
should work, as the async. request is probably not resolved by the time, the page is compiled by vue. For that have a look at the vue component lifecycle.
As the data attributes are reactive, your component will be visible and the value within will be updated couple ms later than it is compiled.
I'd be glad if somebody could suggest another approach as do not if this is the best practice myself.

Uncaught error when trying to embed a Vega chart in Vue + Vuex project

I am trying to use vega-embed within a Vue.js (together with vuex for state management) project. Basically the backend serves a Vega json object which is picked up by the frontend via HTTP GET request with a click event. However I have to click twice to get the plot displayed and the first click event always triggers an error "Uncaught (in promise) TypeError: Cannot read property '$schema' of null". Can someone help me debug? Very much appreciated. Details shown below:
The vue component file:
<template>
<button #click.native="fetchCars(); displayVegaPlot()">fetch cars</button>
<div id="vega-example"></div>
</template>
<script>
import {default as vegaEmbed} from 'vega-embed'
import {
mapState
} from 'vuex'
export default {
name: 'VegaExample',
props: {
component_msg: String
},
methods: {
fetchCars () {
this.$store.dispatch('fetchCars')
},
displayVegaPlot () {
vegaEmbed('#vega-example', this.vega_cars, {actions: false})
}
},
computed: {
...mapState([
'vega_cars'
])
}
}
</script>
... and the store js file:
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
Vue.use(Vuex)
export default new Vuex.Store({
strict: true,
state: {
error: '',
vega_cars: null
},
mutations: {
SET_CARS: (state, cars) => {
state.vega_cars = cars
},
SET_ERROR: (state, error) => {
state.error = error
}
}
actions: {
fetchCars: (context) => {
axios.get(`vega_cars`)
.then(response => context.commit('SET_CARS', response.data))
.catch(error => context.commit('SET_ERROR', error))
}
}
Thanks a lot for the comment from #TommyF, after some doc reading I think I figured out the solution (apparently as a newcomer to web app dev and Vue I didn't know Vue offers a special watch facility). So in the component Vue file, instead of declaring a method displayVegaPlot to call imperatively, a watch can be setup to do the display the Vega plot, as soon as vega_cars changes value:
watch: {
vega_cars: (spec) => {
console.log('$store.state.vega_cars changed value')
if (spec) {
vegaEmbed('#vega-example', spec, {actions: false})
}
}
}
and of course ...mapState(['vega_cars']) needs to be put in computed.

Chain js function and then method in vue.js

Within a Vue component, I am calling a function from a separate JS file. I then need to call a method in my component just after this first function is completed:
My .vue component:
import myFunction from '#/components/functions';
export default {
name: 'test',
components: {
myFunction,
},
created(){
if (....) {
myFunction.function1(myParam)
.then((response) => {
this.method2();
});
},
methods:{
method2(){
something;
},
}
};
My separate functions.js file:
export default {
function1(myParam) {
...
return true;
},
};
I tried several things such as the last one shown in my code which gives me a
.function1(...).then is not a function
I am sure it is not that complicated but can not find the correct syntax.
The function in your other file could return a Promise or it can except a callback from your view component. Also, if you set this equal to self/vm and then use vm.method2(), this is because in the then callback this is defined in scope of that function not the Vue component.

Pass prop as module name when mapping to namespaced module

I'm trying to pass the store module namespace via props to a component. When I try and map to getters with the prop, it throws this error,
Uncaught TypeError: Cannot convert undefined or null to object
If I pass the name as a string it works.
This Works
<script>
export default {
props: ['store'],
computed: {
...mapGetters('someString', [
'filters'
])
}
}
</script>
This does not work
this.store is defined
this.store typeof is a String
<script>
export default {
props: ['store'],
computed: {
...mapGetters(this.store, [
'filters'
])
}
}
</script>
I used this style utilising beforeCreate to access the variables you want, I used the props passed into the component instance:
import { createNamespacedHelpers } from "vuex";
import module from '#/store/modules/mymod';
export default {
name: "someComponent",
props: ['namespace'],
beforeCreate() {
let namespace = this.$options.propsData.namespace;
const { mapActions, mapState } = createNamespacedHelpers(namespace);
// register your module first
this.$store.registerModule(namespace, module);
// now that createNamespacedHelpers can use props we can now use neater mapping
this.$options.computed = {
...mapState({
name: state => state.name,
description: state => state.description
}),
// because we use spread operator above we can still add component specifics
aFunctionComputed(){ return this.name + "functions";},
anArrowComputed: () => `${this.name}arrows`,
};
// set up your method bindings via the $options variable
this.$options.methods = {
...mapActions(["initialiseModuleData"])
};
},
created() {
// call your actions passing your payloads in the first param if you need
this.initialiseModuleData({ id: 123, name: "Tom" });
}
}
I personally use a helper function in the module I'm importing to get a namespace, so if I hadmy module storing projects and passed a projectId of 123 to my component/page using router and/or props it would look like this:
import { createNamespacedHelpers } from "vuex";
import projectModule from '#/store/project.module';
export default{
props['projectId'], // eg. 123
...
beforeCreate() {
// dynamic namespace built using whatever module you want:
let namespace = projectModule.buildNamespace(this.$options.propsData.projectId); // 'project:123'
// ... everything else as above with no need to drop namespaces everywhere
this.$options.computed = {
...mapState({
name: state => state.name,
description: state => state.description
})
}
}
}
Hope you find this useful.
I tackled this problem for hours, too. Then I finally came up with one idea.
Add attachStore function in a child vue component. A function nama is not important. Any name is ok except vue reserved word.
export default {
:
attachStore (namespace) {
Object.assign(this.computed, mapGetters(namespace, ['filters']))
}
}
When this vue component is imported, call attachStore with namespace parameter. Then use it at parent components attributes.
import Child from './path/to/child'
Child.attachStore('someStoresName')
export default {
name: 'parent',
components: { Child }
:
}
The error you're encountering is being thrown during Vue/Vuex's initialization process, this.store cannot be converted because it doesn't exist yet. I haven't had to work with namespacing yet, and this is untested so I don't know if it will work, but you may be able to solve this problem by having an intermediary like this:
<script>
export default {
props: ['store'],
data {
namespace: (this.store !== undefined) ? this.store : 'null',
},
computed: {
...mapGetters(this.namespace, [
'filters'
])
}
}
</script>
That ternary expression will return a string if this.store is undefined, if it isn't undefined then it will return the value in this.store.
Note that there is also a discussion about this on Vue's Github page here: https://github.com/vuejs/vuex/issues/863
Until Vue formally supports it, I replaced something like
...mapState({
foo: state => state.foo
})
with
foo () {
return this.$store.state[this.namespace + '/foo'] || 0
}
Where namespace is passed to my child component using a prop:
props: {
namespace: { type: String, required: true }
}

Categories

Resources