Mysterywood
Mysterywood

Reputation: 1668

How do I make Vue 2 Provide / Inject API reactive?

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) }
        }
    }
}

Upvotes: 15

Views: 24153

Answers (5)

Arik
Arik

Reputation: 6501

As others mentioned, you'll need to provide a reactive object instead of a value.

You can pass a Proxy that wraps around the component's $data object, and exposing only selected properties.

for example:

// App.vue
export default {
    data() {
        return {
            checkout_info: {},
        }
    },
    provide() {
        const exposedProps = ['checkout_info']; // <--- notice this list of properties to be exposed
        const providedState = new Proxy(this.$data, {
            get(target, prop, receiver) {
                return exposedProps.includes(prop) ? Reflect.get(...arguments) : undefined;
            },
            ownKeys(target) {
                return Reflect.ownKeys(target).filter(key => exposedProps.includes(key));
            }
        });
        return {
            providedState
        }
    }
}


// SomeComponent.vue
export default {
    inject: ['providedState']
    computed: {
        deliveryAddress: {
            get() { return this.providedState.checkout_info.delivery_address },
            set(value) { return this.providedState.checkout_info.delivery_address = value; }
        }
    }
}

To expose multiple data properties, you only need to add their keys to the exposedProps array.

I usually use a tweaked version of the proxy to make the props read-only.

Upvotes: 0

Mobin Samani
Mobin Samani

Reputation: 275

If you are using Vue.js 2.7 or later, which has Composition API built-in, you can import and use 'computed' to make provided data reactive:

Parent Component:

import { computed } from 'vue'

export default {
  provide() {
    return {
      foo: computed(() => this.bar)
    }
  },
  data() {
    return {
      bar: 'Hello World'
    }
  }
}

Child Component:

export default {
  inject: ['foo']
}

Upvotes: 0

nbl7
nbl7

Reputation: 551

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.

Upvotes: 4

Anton
Anton

Reputation: 61

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

Upvotes: 6

Mysterywood
Mysterywood

Reputation: 1668

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

Upvotes: 23

Related Questions