How do I make Vue 2 Provide / Inject API reactive? - vue.js

I set up my code as follows, and I was able to update checkout_info in App.vue from the setter in SomeComponent.vue, but the getter in SomeComponent.vue is not reactive.
// App.vue
export default {
provide() {
return {
checkout_info: this.checkout_info,
updateCheckoutInfo: this.updateCheckoutInfo
}
},
data() {
return {
checkout_info: {},
}
},
methods: {
updateCheckoutInfo(key, value) {
this.checkout_info[key] = value
}
}
}
// SomeComponent.vue
export default {
inject: ['checkout_info', 'updateCheckoutInfo']
computed: {
deliveryAddress: {
get() { return this.checkout_info.delivery_address }, // <---- Not reactive??
set(value) { return this.updateCheckoutInfo('delivery_address', value) }
}
}
}

I found the answer after many hours of searching. You have to use Object.defineProperty to make it reactive. I'm not sure if this is the best approach, but this is a working example.
export default {
data() {
return {
checkout_info: {},
}
},
provide() {
const appData = {}
Object.defineProperty(appData, "checkout_info", {
enumerable: true,
get: () => this.checkout_info,
})
return {
updateCheckoutInfo: this.updateCheckoutInfo,
appData,
}
}
}
You can later access it via this.appData.checkout_info

This note from official documentation.
Note: the provide and inject bindings are NOT reactive. This is
intentional. However, if you pass down an observed object, properties
on that object do remain reactive.
I think this is the answer to your question.
source:
https://v2.vuejs.org/v2/api/#provide-inject

I would put the values in an object:
var Provider = {
provide() {
return {
my_data: this.my_data
};
},
data(){
const my_data = {
foo: '1',
fuu: '2'
};
return {
my_data;
}
}
}
var Child = {
inject: ['my_data'],
data(){
console.log(my_data.foo);
return {};
},
}
When are object properties they are reactive. I don't know if this is the correct solution but it works in my case.

Related

Unabled to use Vuex Getter with params

I have Vuex Store that will look like this
const config = {
featureA: { isEnabled: true, maxUser: 2 },
featureB: { isEnabled: false, maxData: 5 },
}
const actions = {
getDataCompany(context, payload) {
return new Promise(async (resolve, reject) => {
try {
const result = await firebase.firestore().collection(payload.collection).doc(payload.companyId).get()
if (result) {
if (payload.isLogin) await context.commit('setConfig', result.data())
return resolve(result.data())
}
reject(new Error('Fail To Load'))
} catch (e) {
reject(new Error('Connection Error'))
}
})
}
}
const mutations = {
setConfig(state, payload) {
state.config = payload
}
}
const getters = {
getData: ({ config }) => (feature, key) => {
const state = config
if (state) if (state[feature]) if (state[feature][key]) return state[feature][key]
return null
}
}
export default new Vuex.Store({
state: { config },
actions: { ...actions },
mutations: { ...mutations },
getters: { ...getters }
})
It's working fine with this method to get the data
computed: {
featureAEnabled() {
return this.$store.getters.getData('featureA', 'isEnabled')
},
}
But I have a problem when the data is change, the value is not update in component, and now I want to use mapGetters because it say can detect changes, But I have problem with the documentation and cannot find how to pass params here,
import { mapGetters } from 'vuex'
computed: {
...mapGetters({
featureAEnabled: 'getData'
})
}
I'am calling the action from here
async beforeMount() {
await this.$store.dispatch('getDataCompany', {collection: 'faturelsit', companyId: 'asep', isLogin: true})
}
And try to detect change in here
mounted() {
if (this.featureAEnabled) console.log('feature enabled')
}
The value change is not detected, and need to refresh twice before the changes is implemented in component
My main target is to detect if there any data change in Vuex and make action in component,
nevermind just working with watch without mapgetter,
I just realize that computed cannot re-run the mounted, so I make method that will called when the variable change in watch. thank you.
The main purpose is fulfilled, but the mapgetter with params is still not answered. so if anyone want to answer please share the way to use mapgetter with params.
You could try to use get and set methods for your computed property.
Example:
computed: {
featureAEnabled: {
get() {
return this.$store.getters.getData('featureA', 'isEnabled')
},
set(value) {
...update featureEnabled property in vuex store
}
},
}

Vue: Do watchers on deep nested objects log oldVal and newVal?

I have a watcher on a deep nested object. I am using Vue.set() to add a reactive property to the object. This is triggering the watcher but the both the newVal and oldVal console logs are showing the data with the new property added to it already rather than the oldVal showing what it was prior to adding the new property to it.
<button #click="addData">Add</button>
data() {
return {
myData: {
time: {
}
}
}
},
watch: {
myData: {
handler(newVal, oldVal) {
console.log("NEW", newVal);
console.log("OLD", oldVal);
},
deep: true
}
},
methods: {
addData() {
this.$set(this.myData.time, 'other','testing')
}
}
As mentioned by #Terry, vue doesnt keep a referency to the oldValue, see what the docs says:
Note: when mutating (rather than replacing) an Object or an Array, the old value will be the same as new value because they reference the same Object/Array. Vue doesn’t keep a copy of the pre-mutate value.
There is some ways you can solve that:
Using vanilla javascript
Add a computed property that returns your data property converting to a JSON string.
Use JSON.parse on your Watch function to convert the string back to a object.
data() {
return {
myData: {
time: {
}
}
}
},
computed: {
computedMyData() {
return JSON.stringify(this.myData);
}
},
watch: {
computedMyData: {
handler(newValJSON, oldValJSON) {
let newVal = JSON.parse(newValJSON),
oldVal = JSON.parse(oldValJSON);
console.log("NEW", newVal);
console.log("OLD", oldVal);
},
deep: true
}
},
methods: {
addData() {
this.$set(this.myData.time, 'other','testing')
}
}
Fiddle: https://jsfiddle.net/mLbuf6t0/
Using LODASH
Add a computed property that returns a deep clone from your data property with cloneDeep function.
data() {
return {
myData: {
time: {
}
}
}
},
computed: {
computedMyData() {
return _.cloneDeep(this.myData)
}
},
watch: {
computedMyData: {
handler(newVal, oldVal) {
console.log("NEW", newVal);
console.log("OLD", oldVal);
},
deep: true
}
},
methods: {
addData() {
this.$set(this.myData.time, 'other','testing')
}
}
Fiddle: https://jsfiddle.net/mLbuf6t0/2/

Vue: Functional component doesn't receive props

The component smart-list does it's job and is rendering the correct component.
It just doesn't pass on the props. I'd expect them to be in a context.data but it is undefined.
SmartList.vue
import EmptyList from "./EmptyList";
import FullList from "./FullList";
export default {
functional: true,
props: {
items: {
type: Array
}
},
render(h, { props, data, children }) {
if (props.items.length > 0) {
return h(FullList, data, children);
} else {
return h(EmptyList, data, children);
}
}
};
I have prepared a codesandbox example
What do I miss?
I have found the solution. In the smart-list component I've changed one line:
import EmptyList from "./EmptyList";
import FullList from "./FullList";
export default {
functional: true,
props: {
items: {
type: Array
}
},
render(h, { props, data, children }) {
if (props.items.length > 0) {
- return h(FullList, data, children);
+ return h(FullList, { attrs: props }, children);
} else {
return h(EmptyList, data, children);
}
}
};
Now it works.
Can someone point me why passing the full data object doesn't work? 🤔

Access vue instance from handsontable

I am trying to set a vuejs variable from within handsontable.
The vuejs variable:
this.dataChanged
in code block below is not available from handsontable settings, any idea how can I access it?
<template>
<div id="hot-container">
<HotTable :root="root" :settings="hotSettings"></HotTable>
</div>
</template>
<script>
export default {
data() {
return {
#vuejs variable i want to set from hot
dataChanged: false,
root: 'test-hot',
hotSettings: {
data: [{something: 0}],
afterChange: function(changes, src) {
if (src !== 'loadData') {
this.dataChanged = true
}
},
methods: {
saveChanges: function () {
if (this.dataChanged){
//save data
}
}
}
I faced this same problem... I found a workaround posted on GitHub like so..
This way you can access all Vue's data, methods, etc as you normally would.
data() {
return {
hotSettings: {
...
afterChange: this.afterChangeVue
...
}
}
},
methods: {
afterChangeVue(changes, source) {
console.log('changes, source => ', changes, source);
console.log('this.$store => ', this.$store);
},
Here is the link to the original thread: https://github.com/handsontable/vue-handsontable-official/issues/7#issuecomment-356190395
I ended up saving to a variable declared outside of vue - ie above the data () declaration
var myNewVar = 42
data() {
#can save to myNewVar from here
Just like #Rosdi_Kasim said, but much simpler with fat arrow function
data() {
return {
hotSettings: {
/*...*/
afterChange: (changes, source) => {
console.log('changes, source => ', changes, source);
console.log('this.$store => ', this.$store);
}
/*...*/
}
}
},
methods: {
}

How to re-render vue js using emit event

I have a global.js and I'm emitting an event to global.js What I want to achieve is whenever the value of my global.js re-render the vue.
global.js
export let globalStore = new Vue({
data: {
translateBool: 0,
about: [`About Us`,`フィリピンのマニラに 2015年9月に設立。`]
},
methods: {
changeLanguage(){
if(this.translateBool == 0){
this.translateBool= 1
}else{
this.translateBool= 0
}
}
}
})
globalStore.$on('changeLanguage',globalStore.changeLanguage)
click.vue
import { globalStore } from '../../global.js';
export default{
name: "sample",
data(){
return{
language: globalStore.translate
}
},
methods : {
changeLanguage(){
globalStore.$emit('changeLanguage')
},
}
}
}
</script>
{{language}}
Even though translateBool is = 1, the output doesn't change
The data properties set in the data method are only set once during the Vue instance's initialization.
If you want the language property to update based on the current state of the globalStore.language value, you should make it a computed property:
export default {
name: "sample",
computed: {
language() {
return globalStore.translate
}
},
methods: {
changeLanguage() {
globalStore.$emit('changeLanguage')
}
}
}