'error during evaluation' for computed property - vue.js

I have an issue where a computed property only sometimes works. Sometimes it has the value/error templateComponent:"(error during evaluation)"
What could be causing this issue? If someone can point me in the correct direction I can investigate further but I dont know where to start.
The problem computed property:
// Error in the below computed property
templateComponent() {
let template = 'default' // assign default template
if (!_.isNull(this.wp.template) && this.wp.template.length)
template = this.wp.template.replace('.php','').toLowerCase()
return template
}
Page.vue
<template>
<div v-if="wp">
<component :is="templateComponent" v-bind:wp="wp"></component>
</div>
<p v-else>Loading...</p>
</template>
<script type="text/javascript">
import { mapGetters } from 'vuex'
import * as Templates from './templates'
// Map template components
let templateCmps = {}
_.each(Templates, cmp => {
templateCmps[cmp.name] = cmp
})
export default {
props: ["slug"],
components: {
...templateCmps
// Example of templateCmps is below
// 'default': Templates.Default,
// 'agency': Templates.Agency,
// 'home': Templates.Home,
},
computed: {
...mapGetters(['pageBySlug']),
wp() {
return this.pageBySlug(this.slug);
},
// Error in the below computed property
templateComponent() {
let template = 'default' // assign default template
if (!_.isNull(this.wp.template) && this.wp.template.length)
template = this.wp.template.replace('.php','').toLowerCase()
return template
}
},
created() {
// Get page title, content, etc. via rest request
this.$store.dispatch('getPageBySlug', { slug: this.slug })
}
}
</script>

The problem might relate to this.wp.template. Are you certain that it is always a string upon which lowerCase can be called? If the computed property would return something else than a string, it could cause the problem.
Also, Vue has problems handling dashes before numbers.

**> must be added:
> --> import store from "./store/store";
> --> new Vue({ .... store ....**
import store from "./store/store";
Vue.config.productionTip = false;
new Vue({
router,
store,
render: (h) => h(App),
}).$mount("#app");

Related

Overriding the value of a global variable

I just created two variables $isCompany and $isLoggedIn in my main.js.
import Vue from "vue";
import App from "./App.vue";
import "./registerServiceWorker";
import router from "./router";
Vue.config.productionTip = false;
Vue.prototype.$isCompany = true;
Vue.prototype.$isLoggedIn = true;
new Vue({
router,
render: h => h(App)
}).$mount("#app");
My Question:
How can I access these variables in my <Button> component in Navigation.vue?
v-ifs are working but I'm not able to toggle the value of the variable, so I think there is an issue with overriding the value.
<div v-if="this.$isLoggedIn" class="header-left">
<User v-if="this.$isCompany" name="Company" img="..."></User>
<User v-if="!this.$isCompany" name="Not Company" img="..."></User>
<Button #onclick="this.$isCompany = !this.$isCompany" :color="'grey'" :isIconOnly="true"><b-icon icon="arrow-down-up"></b-icon></Button>
</div>
If you need it, here is the way how I bind the #onclick to my <Button> component:
<button #click="$emit('onclick')" :class="['button', 'size-'+size, 'color-'+color, isRounded ? 'type-rounded' : '', isIconOnly ? 'type-icon-only' : '']">
EDIT:
Just noticed this error in my console if I click the button:
v-ifs are working
Are you sure? I expect v-if="this.$isLoggedIn" not to work because this should not be used here (it is redundant; there is an implicit this. already there so you're basically doing this.this.$isLoggedIn).
but I'm not able to toggle the value of the variable
This is probably because you have not defined the variables in a "reactive" way.
Vue.prototype.$isCompany = true;
Vue.prototype.$isLoggedIn = true;
It feels a bit awkward to define the variables like this on the Vue prototype object.
You can define the global variables in a reactive way by defining them as data properties of the root Vue instance:
new Vue({
router,
render: h => h(App),
data: {
isCompany: false,
isLoggedIn: false,
}
}).$mount("#app");
You can access them from any descendant component via this.$root.isCompany. So, in your template, you would do:
<User v-if="$root.isCompany" name="Company" img="..."></User>
If you really want to avoid doing $root. in the template, then you can define proxy properties on the Vue prototype object (in addition to the above):
Object.defineProperties(Vue.prototype, {
$isCompany: {
get() { return this.$root.isCompany },
set(x) { this.$root.isCompany = x }
},
$isLoggedIn: {
get() { return this.$root.isLoggedIn },
set(x) { this.$root.isLoggedIn = x }
},
})
Now you can do this in the template:
<User v-if="$isCompany" name="Company" img="..."></User>
Here is a small demo:
Object.defineProperties(Vue.prototype, {
$isCompany: {
get() { return this.$root.isCompany },
set(x) { this.$root.isCompany = x }
}
})
Vue.component('child', {
template: '<div><input type="checkbox" v-model="$isCompany"> {{ $isCompany }}</div>'
})
new Vue({
el: '#app',
data: {
isCompany: false,
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<child></child>
</div>

Vuejs clone object in script tag or inside instance e.g. data

Wondering what is the best practice for such code here and what is the difference when cloning an object inside script tag or doing it in data property:
<script>
import {cloneDeep} from "lodash";
import {INVITE_USER_FORM_FIELDS} from './data';
const FORM_FIELDS = cloneDeep(INVITE_USER_FORM_FIELDS);
export default {
name: "ModalInviteCreate",
data() {
return {
FORM_FIELDS,
};
},
OR
<script>
import {cloneDeep} from "lodash";
import {INVITE_USER_FORM_FIELDS} from './data';
export default {
name: "ModalInviteCreate",
data() {
return {
FORM_FIELDS: cloneDeep(INVITE_USER_FORM_FIELDS),
};
},
The difference is the data method will run every time you create a new instance of that component. If you never need to recompute a deep clone, then option 1 is preferable since the extra clones are a waste. If you're bothering to create a deep clone though, I'm guessing it's so your components can safely mutate the object without modifying the original. So option 2 is probably the best choice, otherwise all of the component instances would all share the same object.
Here's an example to illustrate, see the console.logs.
const fakeDeepClone = name => {
console.log(`creating data for component ${name}`);
return { name };
}
const aData = fakeDeepClone('a');
const componentA = {
template: '<div>name: {{name}}</div>',
data() {
return aData
}
}
const componentB = {
template: '<div>name: {{name}}</div>',
data() {
return fakeDeepClone('b')
}
}
var app = new Vue({
el: '#app',
components: {
componentA,
componentB
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<component-a></component-a>
<component-a></component-a>
<component-b></component-b>
<component-b></component-b>
</div>

How watch global variable in component vuejs?

I need global variables for errors. But I don't want set input variable for every component.
How I can watch $errors in component ABC without input variable?
(without <abc :errors="$errors"></abc>)
index.js:
Vue.prototype.$errors = {};
new Vue({
el: '#app',
render: h => h(App),
}
App.vue:
...
name: 'App',
components: {
ABC
}
...
methods:{
getContent() {
this.$errors = ...from axis...
}
Component ABC:
<template>
<div>{{ error }}</div>
</template>
...
watch: {
???
}
Here's an example of how it could be done:
const errors = Vue.observable({ errors: {} })
Object.defineProperty(Vue.prototype, '$errors', {
get () {
return errors.errors
},
set (value) {
errors.errors = value
}
})
new Vue({
el: '#app',
methods: {
newErrors () {
// Generate some random errors
const errors = {}
for (const property of ['name', 'type', 'id']) {
if (Math.random() < 0.5) {
errors[property] = 'Invalid value'
}
}
this.$errors = errors
}
}
})
new Vue({
el: '#app2',
watch: {
$errors () {
console.log('$errors has changed')
}
}
});
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<pre>{{ $errors }}</pre>
<button #click="newErrors">New errors</button>
</div>
<div id="app2">
<pre>{{ $errors }}</pre>
</div>
I've created two Vue instances to illustrate that the value really is shared. Clicking the button in the first instance will update the value of $errors and the watch is triggered in the second instance.
There are a few tricks in play here.
Firstly, reactivity can only track the reading and writing of properties of an observable object. So the first thing we do is create a suitable object:
const errors = Vue.observable({ errors: {} })
We then need to wire this up to Vue.prototype.$errors. By defining a get and set for that property we can proxy through to the underlying property within our observable object.
All of this is pretty close to how data properties work behind the scenes. For the data properties the observable object is called $data. Vue then uses defineProperty with get and set to proxy though from the Vue instance to the $data object, just like in my example.
as Estradiaz said:
You can use Vuex and access the value outside of Vue like in this answer: https://stackoverflow.com/a/47575742/10219239
This is an addition to Skirtles answer:
You can access such variables via Vue.prototype.variable.
You can set them directly, or use Vue.set, it works either way.
My code (basically the same as Skirtless):
main.js
const mobile = Vue.observable({ mobile: {} });
Object.defineProperty(Vue.prototype, '$mobile', {
get() { return mobile.mobile; },
set(value) { mobile.mobile = value; }
});
function widthChanged() {
if (window.innerWidth <= 768) {
if (!Vue.prototype.$mobile) Vue.set(Vue.prototype, '$mobile', true);
} else if (Vue.prototype.$mobile) Vue.set(Vue.prototype, '$mobile', false);
}
window.addEventListener("resize", widthChanged);
widthChanged();
Home.vue:
watch: {
'$mobile'(newValue) {
// react to Change in width
}
}

Vue component computed not reacting

I have 2 components OperatorsList and OperatorButton.
The OperatorsList contains of course my buttons and I simply want, when I click one button, to update some data :
I emit select with the operator.id
This event is captured by OperatorList component, who calls setSelectedOperator in the store
First problem here, in Vue tools, I can see the store updated in real time on Vuex tab, but on the Components tab, the operator computed object is not updated until I click antoher node in the tree : I don't know if it's a display issue in Vue tools or a real data update issue.
However, when it's done, I have another computed property on Vue root element called selectedOperator that should return... the selected operator : its value stays always null, I can't figure out why.
Finally, on the button, I have a v-bind:class that should update when the operator.selected property is true : it never does, even though I can see the property set to true.
I just start using Vue, I'm pretty sure I do something wrong, but what ?
I got the same problems before I used Vuex, using props.
Here is my OperatorList code :
<template>
<div>
<div class="conthdr">Operator</div>
<div>
<operator-button v-for="operator in operators" :op="operator.id"
:key="operator.id" #select="selectOp"></operator-button>
</div>
</div>
</template>
<script>
import OperatorButton from './OperatorButton';
export default {
name: 'operators-list',
components : {
'operator-button': OperatorButton
},
computed : {
operators() { return this.$store.getters.operators },
selected() {
this.operators.forEach(op =>{
if (op.selected) return op;
});
return null;
},
},
methods : {
selectOp(arg) {
this.$store.commit('setSelectedOperator', arg);
}
},
}
</script>
OperatorButton code is
<template>
<span>
<button type="button" v-bind:class="{ sel: operator.selected }"
#click="$emit('select', {'id':operator.id})">
{{ operateur.name }}
</button>
</span>
</template>
<script>
export default {
name: 'operator-button',
props : ['op'],
computed : {
operator() {
return this.$store.getters.operateurById(this.op);
}
},
}
</script>
<style scoped>
.sel{
background-color : yellow;
}
</style>
and finally my app.js look like that :
window.Vue = require('vue');
import Vuex from 'vuex';
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex';
const store = new Vuex.Store({
state: {
periods : [],
},
mutations: {
setInitialData (state, payload) {
state.periods = payload;
},
setSelectedOperator(state, payload) {
this.getters.operateurs.forEach( op => {
op.selected = (op.id==payload.id)
})
},
},
getters : {
operators : (state) => {
if (Array.isArray(state.periods))
{
let ops = state.periods
.map( item => {
return item.operators
}).flat();
ops.forEach(op => {
// op.selected=false; //replaced after Radu Diță answer by next line :
if (ops.selected === undefined) op.selected=false;
})
return ops;
}
},
operatorById : (state, getters) => (id) => {
return getters.operators.find(operator => operator.id==id);
},
}
});
import Chrono from './components/Chrono.vue';
var app = new Vue({
el: '#app',
store,
components : { Chrono },
mounted () {
this.$store.commit('setInitialData',
JSON.parse(this.$el.attributes.initialdata.value));
},
computed: {
...mapState(['periods']),
...mapGetters(['operators', 'operatorById']),
selectedOperator(){
this.$store.getters.operators.forEach(op =>{
if (op.selected) return op;
});
return null;
}
},
});
Your getter in vuex for operators is always setting selected to false.
operators : (state) => {
if (Array.isArray(state.periods))
{
let ops = state.periods
.map( item => {
return item.operators
}).flat();
ops.forEach(op => {
op.selected=false;
})
return ops;
}
}
I'm guessing you do this for initialisation, but that's a bad place to put it, as you'll never get a selected operator from that getter. Just move it to the proper mutations. setInitialData seems like the right place.
Finally I found where my problems came from :
The $el.attributes.initialdata.value came from an API and the operator objects it contained didn't have a selected property, so I added it after data was set and it was not reactive.
I just added this property on server side before converting to JSON and sending to Vue, removed the code pointed by Radu Diță since it was now useless, and it works.

How to access the window object in vue js?

I have this vue js component:
<template>
<div>
hello world
</div>
</template>
<script>
export default {
name: 'mycomp',
data: function () {
console.error("window.google_recaptcha_public_key", window.google_recaptcha_public_key);
return {
}
},
mounted() {
let app = this;
console.error("window.google_recaptcha_public_key2", window.google_recaptcha_public_key);
},
}
</script>
<style scoped lang="scss">
</style>
returns:
window.google_recaptcha_public_key undefined
window.google_recaptcha_public_key2 undefined
where can I leave painless and happy all global configuration?
notice this configuration lives in my laravel backend. So I wont copy paste all values from the backend to the front end
U can use Vue.prototype in main.js file, or in file you import Vue
Vue.prototype.Hereanyname = window.hereanyname;
and in your Vue application, you can use it
Hereanyname.thefunction
Real example on Laravel
in main.js
import Vue from 'vue';
Vue.prototype.Routes = window.routes;
new Vue({
el: '#app',
template: '<App/>',
components: {App}
});
in your application
:href="Routes.route('laravel.route.here')"
So for your case
import Vue from 'vue';
Vue.prototype.GoogleRecaptcha = window.google_recaptcha_public_key;
new Vue({
el: '#app',
template: '<App/>',
components: {App}
});
inside application
mounted() {
console.log(this.GoogleRecaptcha)
}
In Vue3, you no longer have the global Vue instance, so you need to assign the window as a global property on your app...
// main.js
app.config.globalProperties.window = window
Then in your components, window will just work.
This info is from an impeccable source.
You should save your window variable in Vue.prototype
main.js
Vue.prototype.$authUser = window.authUser;
After that, you can access your data as follows:
Vue template
<div v-text="$authUser.name"></div>
Vue script
let name = this.$authUser.name;
window is available in the vuex store. This may help if you need to mutate the window property synchronously with other actions/mutations, give you a chance to validate what goes into it, or catch an error if the variable you intend to put there isn't available.
export default new Vuex.store({
state: {
windowFoo: window.foo,
googleRecaptcha: window.google_recaptcha_public_key
},
getters: {
windowFoo: (state) => state.windowFoo,
googleRecaptcha: (state) => state.googleRecaptcha
},
actions: {
barThenFooThenBaz({ commit }, { foo }) {
// ... do some other required actions first
commit("setWindowFoo", foo);
// ... do some dependent actions next
}
},
mutations: {
setWindowFoo(state, foo) {
state.windowFoo = foo;
}
}
});
Then from your Single File Component...
//....
computed: {
windowFoo() {
return this.$store.getters.windowFoo;
},
googleRecaptcha() {
return this.$store.getters.googleRecaptcha;
}
},
methods: {
async barThenFooThenBaz(foo) {
await this.$store.dispatch({
type: "barThenFooThenBaz",
foo: foo
});
// ... do something dependent on windowFoo being set
}
}
//....
Although the other answers here are totally acceptable, I've had issues using the Vue instance with Vue.prototype in main.js as our project has gotten larger, so I hope this helps!
Provide/Inject works nicely. Here's an example with Vue 3:
main.js
const app = createApp(App)
app.provide('recaptcha_key', window.google_recaptcha_public_key)
app.mount('#app')
MyComponent.vue
<script setup>
const { inject } from 'vue'
const recaptchaKey = inject('recaptcha_key')
</script>