How to set vue-i18n in a component's property reactively - vue.js

I am trying to display some messages or errors depends on an async function result.
When a response's request doesn't include the message property I should use my own.
The problem is that when I use this.$t("message") inside the someMethod method the response.message (data) it isn't reactive. So it won't change after the locale change.
<v-alert v-if="response.error || response.message"
:type="response.error ? 'error' : 'info'">
{{response.error ? response.error : response.message}}
</v-alert>
export default {
data() {
return {
response: {
message: "",
error: ""
}
};
},
methods: {
async someMethod() {
const apiResponse = await sendRequestToAnApi();
if (apiResponse.message) {
this.response.message = apiResponse.message;
} else {
this.response.message = this.$t("translation")
}
}
}
}
I guess I should use computed property to make it work, but I don't have a clue how to mix response object with possible API response.
Any help will be appreciated

You could have your API return not only a message but also a code, where the message could be a textual string based on user locale settings, and the code a slug that will always be English, like users.added or project.created. You can store this code in response.code and create a computed property checking this code and returning the correct vue-i18n translation.
export default {
data() {
return {
response: {
code: "",
error: ""
}
};
},
methods: {
async someMethod() {
const apiResponse = await sendRequestToAnApi();
if (apiResponse.code) {
this.response.code = apiResponse.code;
} else {
this.response.code = null
}
}
},
computed: {
translatedMessage () {
if (this.response.code === 'users.added') {
return this.$t('translate')
}
return 'unknown code'
}
}
}
Of course you could also make this work with a textual string coming from the API, but personally I like working with a specific code that is independent from language settings.

Related

VueJS | Method "watch" has type "object" in the component definition

Currently I have following Watches in Product.vue file
watch: {
isOnline: {
async handler (isOnline) {
if (isOnline) {
const maxQuantity = await this.getQuantity();
this.maxQuantity = maxQuantity;
}
}
},
isMicrocartOpen: {
async handler (isOpen) {
if (isOpen) {
const maxQuantity = await this.getQuantity();
this.maxQuantity = maxQuantity;
}
},
immediate: true
},
isSample (curr, old) {
if (curr !== old) {
if (!curr) {
console.log('send the updateCall', curr);
// this.updateProductQty(this.product.qty);
pullCartSync(this);
}
}
}
}
but I am getting this following error (Vue Warn) in console
[Vue warn]: Method "watch" has type "object" in the component definition. Did you reference the function correctly?
I'm not sure why i am getting this error, as the syntax i am using seems to be correct, and its even functioning right.
Any suggestions why its giving this warning in error console?
Update:
Location where i have used the watch in vue page.
You have something like methods: { watch: {} } in your component definition. That's why vue is complaining. That might be added by a mixin as well.

Setting intial local data from Vuex store giving "do not mutate" error

I thought I understood the correct way to load inital state data from Vuex into the local data of a component, but why is this giving me “[vuex] do not mutate vuex store state outside mutation handlers.” errors! I am using a mutation handler!
I want my component data to start empty, unless coming back from a certain page (then it should pull some values from Vuex).
The component is using v-model=“selected” on a bunch of checkboxes. Then I have the following:
// Template
<grid-leaders
v-if="selected.regions.length"
v-model="selected"
/>
// Script
export default {
data() {
return {
selectedProxy: {
regions: [],
parties: [],
},
}
},
computed: {
selected: {
get() {
return this.selectedProxy
},
set(newVal) {
this.selectedProxy = newVal
// If I remove this next line, it works fine.
this.$store.commit("SET_LEADER_REGIONS", newVal)
},
},
},
mounted() {
// Restore saved selections if coming back from a specific page
if (this.$store.state.referrer.name == "leaders-detail") {
this.selectedProxy = {...this.$store.state.leaderRegions }
}
}
}
// Store mutation
SET_LEADER_REGIONS(state, object) {
state.leaderRegions = object
}
OK I figured it out! The checkbox component (which I didn't write) was doing this:
updateRegion(region) {
const index = this.value.regions.indexOf(region)
if (index == -1) {
this.value.regions.push(region)
} else {
this.value.regions.splice(index, 1)
}
this.$emit("input", this.value)
},
The line this.value.regions.push(region) is the problem. You can't edit the this.value prop directly. I made it this:
updateRegion(region) {
const index = this.value.regions.indexOf(region)
let regions = [...this.value.regions]
if (index == -1) {
regions.push(region)
} else {
regions.splice(index, 1)
}
this.$emit("input", {
...this.value,
regions,
})
},
And then I needed this for my computed selected:
selected: {
get() {
return this.selectedProxy
},
set(newVal) {
// Important to spread here to avoid Vuex mutation errors
this.selectedProxy = { ...newVal }
this.$store.commit("SET_LEADER_REGIONS", { ...newVal })
},
},
And it works great now!
I think the issue is that you can't edit a v-model value directly, and also you also have to be aware of passing references to objects, and so the object spread operator is a real help.

How to define an empty Vuex state and then check it is not empty/null in Vue component?

I have a Vuex store which defines a state as null:
const state = {
Product: null // I could do Product:[] but that causes a nested array of data
};
const getters = {
getProduct: (state) => state.Product
};
const actions = {
loadProduct({commit}, {ProudctID}) {
axios.get(`/api/${ProductID}`).then(response => {commit('setProduct', response.data)})
.catch(function (error) {
//error handler here
}
}
};
const mutations = {
setProduct(state, ProductData) {
state.Product = ProductData
}
};
In my Vue component I want to display the Product data when it is available. So I did this:
<template>
<div v-if="Product.length">{{Product}}</div>
</template>
When I run it, I get an error stating
Vue warn: Error in render: "TypeError: Cannot read property 'length'
of null"
Okay, so I tried this which then does nothing at all (throws no errors and never displays the data):
<template>
<div v-if="Product != null && Product.length">{{Product}}</div>
</template>
What is the correct way to display the Product object data if it is not null? Product is a JSON object populated with data from a database which is like this:
[{ "ProductID": 123, "ProductTitle": "Xbox One X Gaming" }]
My Vue component gets the data like this:
computed:
{
Product() {
return this.$store.getters.getProduct
}
}
,
serverPrefetch() {
return this.getProduct();
},
mounted() {
if (this.Product != null || !this.Product.length) {
this.getProduct();
}
},
methods: {
getProduct() {
return this.$store.dispatch('loadProduct')
}
}
If I look in Vue Dev Tools, it turns out that Product in my Vue component is always null but in the Vuex tab it is populated with data. Weird?
This is a classic case to use computed:
computed: {
product() {
return this.Product || [];
}
}
In the store function when you do the request you can check
examoe
const actions = {
loadProduct({commit}, {ProudctID}) {
if (this.product.length > 0) {// <-- new code
return this.product // <-- new code
} else { // <-- new code
// do the http request
axios.get(`/api/${ProductID}`)
.then(response => {
commit('setProduct', response.data)}
)
.catch(function (error) {
//error handler here
}
}// <-- new code
}
};

Access component's this inside firebase promise reposonse

I've tried to bind it like it doesn't seem to make the trick :)
firebaseInstance.auth().fetchSignInMethodsForEmail(this.signUpData.email)
.then((response) => {
... all logic
}).bind(this)
...since it outputs the following error:
firebaseInstance.auth(...).fetchSignInMethodsForEmail(...).bind is not a function
Here is the component's logic, can someone please suggest a proper way to access this after firebase response resolves? :bowing:
import { VALIDATION_MESSAGES, VALUES } from './signup.module.config'
import GLOBAL_EVENTS from 'values/events'
import { firebaseInstance } from 'database'
export default {
name: `SignUpForm`,
data() {
return {
signUpData: {
email: ``,
password: ``,
confirmPassword: ``
}
}
},
methods: {
onEmailSignUp() {
// Here this is component
console.log(this.$refs)
firebaseInstance.auth().fetchSignInMethodsForEmail(this.signUpData.email)
.then((response) => {
// other logic
} else {
// Here this is lost and equals undefined
this.$refs.email.setCustomValidity(`error`)
}
})
}
}
}
The bind instruction should be used on a function object, not on a function return value.
By doing
firebaseInstance.auth().fetchSignInMethodsForEmail(this.signUpData.email)
.then((response) => {
... all logic
}).bind(this)
You try to use bind on the return of the then method of you promise, which is a promise object and can't use bind.
You can try firebaseInstance.auth().fetchSignInMethodsForEmail(this.signUpData.email)
.then(function(response){
... all logic
}.bind(this))
instead. Here the bind is put on the function send in the promise so it should work correctly. I also transformed the function from arrow function to normal, because I think there is no need for arrow function with bind.
Using ES8 async/await sugar syntax you can do it like this :
async onEmailSignUp () {
try {
const response = await firebaseInstance.auth().fetchSignInMethodsForEmail(this.signUpData.email)
// other logic
} catch (error) {
console.log(error)
this.$refs.email.setCustomValidity(`error`)
}
}

Vue | define reactive property in plugin

Trying to build my own form validation plugin (it's for learning purposes - so I don't use existing libraries).
So I created the following mixin:
export default {
beforeCreate() {
if (! this.$vnode || /^(keep-alive|transition|transition-group)$/.test(this.$vnode.tag)) {
return;
}
// create
this.$validator = new Instance();
// define computed
if (! this.$options.computed) {
this.$options.computed = {};
}
this.$options.computed['errors'] = function() {
return this.$validator.errors;
};
}
}
And loaded the mixin from the component (cause I don't want to see this anywhere):
export default {
name: "SignIn",
components: {
AppLayout,
TextField,
HelperText,
Button
},
mixins: [ValidateMixin]
}
Anyway, anytime input has changed - there is an event which tests the value and controls my errors bag:
export default class {
constructor() {
this.items = {};
}
first(name) {
if (name in this.items) {
return this.items[name][0];
}
return false;
}
add(name, errors) {
this.items[name] = errors;
}
remove(name) {
delete this.items[name];
}
has(name) {
return name in this.items;
}
all() {
return this.items;
}
}
I've bind HTML element (:invalid="errors.has('email')"), and with the devtools I can see the errors bag changing - but the binding is just doesn't work. The invalid property remains false no matter what I'm doing.
I do understand that in order to create reactive property, I've to handle this with getters/setters, but I'm a bit stuck with it.