Vue state not updated with default injected value - javascript

If you click the button, you can see the value of state updated in the console, but it isn't updated in the page output. How can I make it work with a default injected value?
const Component = {
inject: {
state: {
default: () => ({
example: 1
})
}
},
template: `<div>
<div>{{ state }}</div>
<button #click="click">click</button>
</div>`,
methods: {
click() {
this.state.example += 1
console.log(this.state)
}
}
}
new Vue({
el: "#app",
components: {
Component
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<component></component>
</div>
Is it related to as Vue docs say "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'm confused about the difference between the BINDINGS not being reactive but the OBSERVED OBJECT being reactive. Could you show an example to demo the difference?

Sorry, but it is not clear what you want - where's the provider for the "injection"? Why do you use inject in the same component as you use the value itself?
Here's your code, without inject:
1. Use the data attribute
const Component = {
data() {
return {
state: {
example: 1
}
}
},
template: `<div>
<div>{{ state }}</div>
<button #click="click">click</button>
</div>`,
methods: {
click() {
this.state.example += 1
console.log(this.state)
}
}
}
new Vue({
el: "#app",
components: {
Component
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<component></component>
</div>
Just use the data attribute - you can have a default value for example.
2. Use injection
inject is something completely different - it's a way to pass values from a provider to a consumer:
const Component = {
inject: ['state1'],
data() {
return {
state: {
example: 1
}
}
},
template: `<div>
<div>injected: {{ state1 }}</div>
<div>{{ state }}</div>
<button #click="click">click</button>
</div>`,
methods: {
click() {
this.state.example += 1
console.log(this.state)
}
}
}
new Vue({
el: "#app",
provide: {
state1: {
example1: 1
}
},
components: {
Component
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<component></component>
</div>
You can "skip" levels of components and use the provided value in components where you inject it - you don't have to pass it all the way down as props.
3. Create reactive inection
If you want to have reactive injection then you need to pass down something more complex:
const Component1 = {
inject: ['state2'],
data() {
return {
state: {
example: 1
}
}
},
template: `<div>
<div>injected: {{ state2.state2P }}</div>
<div>{{ state }}</div>
<button #click="click">click</button>
</div>`,
methods: {
click() {
this.state.example += 1
console.log(this.state)
}
}
}
new Vue({
el: "#app",
data() {
return {
state2: {
example2: 1
}
}
},
provide() {
// create an object (state2)
const state2 = {}
// define a property on the object (state2P), that
// has a get() function that always gets the provider's
// value you want to inject
Object.defineProperty(state2, 'state2P', {
enumerable: true,
get: () => this.state2,
})
// return the created object (with a property that always
// gets the value in the parent)
return {
state2
}
},
components: {
Component1
},
methods: {
parentClick() {
this.state2.example2 += 1
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<component1></component1>
<button #click="parentClick">PARENT CLICK</button>
</div>
I added a button to the template, so you can see that a method defined in the provider component's scope changes the value displayed in the consumer component's scope. (Also had to change the component's name, as Vue started to "whine" about using a restricted word.)

Related

Vue not reacting to a computed props change

I am using the Vue composition API in one of my components and am having some trouble getting a component to show the correct rendered value from a computed prop change. It seems that if I feed the prop directly into the components render it reacts as it should but when I pass it through a computed property it does not.
I am not sure why this is as I would have expected it to be reactive in the computed property too?
Here is my code:
App.vue
<template>
<div id="app">
<Tester :testNumber="testNumber" />
</div>
</template>
<script>
import Tester from "./components/Tester";
export default {
name: "App",
components: {
Tester,
},
data() {
return {
testNumber: 1,
};
},
mounted() {
setTimeout(() => {
this.testNumber = 2;
}, 2000);
},
};
</script>
Tester.vue
<template>
<div>
<p>Here is the number straight from the props: {{ testNumber }}</p>
<p>
Here is the number when it goes through computed (does not update):
{{ testNumberComputed }}
</p>
</div>
</template>
<script>
import { computed } from "#vue/composition-api";
export default {
props: {
testNumber: {
type: Number,
required: true,
},
},
setup({ testNumber }) {
return {
testNumberComputed: computed(() => {
return testNumber;
}),
};
},
};
</script>
Here is a working codesandbox:
https://codesandbox.io/s/vue-composition-api-example-forked-l4xpo?file=/src/components/Tester.vue
I know I could use a watcher but I would like to avoid that if I can as it's cleaner the current way I have it
Don't destruct the prop in order to keep its reactivity setup({ testNumber }) :
setup(props) {
return {
testNumberComputed: computed(() => {
return props.testNumber;
}),
};
}

Try to pass $ref data to prop data of component

Hello I have a component Carousel, and I have method:
carousel () {
return this.$refs.carousel
}
When I try pass to prop of other component, I get undefined in prop data, why?
<Dots :carousel="carousel()" />
In child component I have:
export default {
name: 'Dots',
props: ['carousel']
}
In mounted when I try call console.log(this.carousel), I get undifined. But I need get a component data of Carousel. How I can do it?
Sandbox: https://codesandbox.io/s/flamboyant-monad-5bhjz?file=/src/components/Test.vue
You can set a function in carousel to return the data you want to send to dots. Then, in the parent component, set its return value to an attribute in data which will be passed as a prop:
const dotsComponent = Vue.component('dotsComponent', {
template: '#dotsComponent',
props: ['carousel']
});
const carouselComponent = Vue.component('carouselComponent', {
template: '#carouselComponent',
data() { return { id:1 } },
methods: {
getData() { return { id: this.id } }
}
});
new Vue({
el: "#app",
components: { dotsComponent, carouselComponent },
data() { return { carousel:null } },
mounted() { this.carousel = this.$refs.carousel.getData(); }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div><carousel-component ref="carousel"/></div>
<div><dots-component :carousel="carousel"/></div>
</div>
<template id="dotsComponent"><p>Dots: {{carousel}}</p></template>
<template id="carouselComponent"><p>Carousel</p></template>
You could pass the ref directly without using a method any other property :
<Dots :carousel="$refs.carousel" />

how to render component from a component in vue js

I have a component with following template:
<div v-for:"item in store" v-bind:key="item.type">
<a>{{item.type}}</a>
</div>
I have another component called 'StoreComponent'
On click of a element in first component I want to clear the current component and show the StoreComponent and able to pass item.type to StoreComponent.
I don't want to use router-link or router.push as I don't want to create a new url but override the current component with the new one depending on the item.type value.
StoreComponent.vue
export default{
name: 'StoreComponent',
props: ['item'],
data: function () {
return {
datum: this.item
}
},
methods: {
//custom methods
}
}
You could use dynamic components and pass the item-type as a prop.
Vue.component('foo', {
name: 'foo',
template: '#foo'
});
Vue.component('bar', {
name: 'bar',
template: '#bar',
props: ['test']
});
new Vue({
el: "#app",
data: {
theComponent: 'foo', // this is the 'name' of the current component
somethingWeWantToPass: {
test: 123 // the prop we are passing
},
},
methods: {
goFoo: function() {
this.theComponent = 'foo';
},
goBar: function() {
this.theComponent = 'bar';
},
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<button #click="goFoo">Foo</button>
<button #click="goBar">Bar</button>
<component :is="theComponent" v-bind="somethingWeWantToPass"></component>
</div>
<template id="foo">
<div>
Foo
</div>
</template>
<template id="bar">
<div>
Bar
<div>This is a prop: {{ this.test }}</div>
</div>
</template>

Vue.js computed property loses its reactivity when passed through an event

I have a Modal component in my main app that gets passed content via an event whenever a modal has to be shown. Modal content is always a list with an action associated with each item, like "select" or "remove":
Vue.component('modal', {
data() {
return {
shown: false,
items: [],
callback: ()=>{}
}
},
mounted() {
EventBus.$on('showModal', this.show);
},
template: `<ul v-if="shown">
<li v-for="item in items">
{{ item }} <button #click="callback(item)">Remove</button>
</li>
</ul>`,
methods: {
show(items, callback) {
this.shown = true;
this.items = items;
this.callback = callback;
}
}
});
Sadly, when passing a computed property to that modal like in the component below, the reactive link gets broken -> if the action is "remove", the list is not updated.
Vue.component('comp', {
data() {
return {obj: {a: 'foo', b: 'bar'}}
},
computed: {
objKeys() {
return Object.keys(this.obj);
}
},
template: `<div>
<button #click="showModal">Show Modal</button>
<modal></modal>
</div>`,
methods: {
remove(name) {
this.$delete(this.obj, name);
},
showModal() {
EventBus.$emit('showModal', this.objKeys, this.remove);
}
}
});
See the minimal use case in this fiddle: https://jsfiddle.net/christophfriedrich/cm778wgj/14/
I think this is a bug - shouldn't Vue remember that objKeys is used for rendering in Modal and update it? (The forwarding of the change of obj to objKeys works.) If not, what am I getting wrong and how could I achieve my desired result?
You have the modal working with its own copy of items:
template: `<ul v-if="shown">
<li v-for="item in items">
{{ item }} <button #click="callback(item)">Remove</button>
</li>
</ul>`,
methods: {
show(items, callback) {
this.shown = true;
this.items = items;
this.callback = callback;
}
}
That copy is made once, upon the call to show, and what you are copying is just the value of the computed at the time you emit the showModal event. What show receives is not a computed, and what it assigns is not a computed. It's just a value.
If, anywhere in your code, you made an assignment like
someDataItem = someComputed;
the data item would not be a functional copy of the computed, it would be a snapshot of its value at the time of the assignment. This is why copying values around in Vue is a bad practice: they don't automatically stay in sync.
Instead of copying values around, you can pass a function that returns the value of interest; effectively a get function. For syntactic clarity, you can make a computed based on that function. Then your code becomes
const EventBus = new Vue();
Vue.component('comp', {
data() {
return {
obj: {
a: 'foo',
b: 'bar'
}
}
},
computed: {
objKeys() {
return Object.keys(this.obj);
}
},
template: `<div>
<div>Entire object: {{ obj }}</div>
<div>Just the keys: {{ objKeys }}</div>
<button #click="remove('a')">Remove a</button>
<button #click="remove('b')">Remove b</button>
<button #click="showModal">Show Modal</button>
<modal></modal>
</div>`,
methods: {
remove(name) {
this.$delete(this.obj, name);
},
showModal() {
EventBus.$emit('showModal', () => this.objKeys, this.remove);
}
}
});
Vue.component('modal', {
data() {
return {
shown: false,
getItems: null,
callback: () => {}
}
},
mounted() {
EventBus.$on('showModal', this.show);
},
template: `<div v-if="shown">
<ul v-if="items.length>0">
<li v-for="item in items">
{{ item }} <button #click="callback(item)">Remove</button>
</li>
</ul>
<em v-else>empty</em>
</div>`,
computed: {
items() {
return this.getItems && this.getItems();
}
},
methods: {
show(getItems, callback) {
this.shown = true;
this.getItems = getItems;
this.callback = callback;
}
}
});
var app = new Vue({
el: '#app'
})
<script src="//unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
<comp></comp>
</div>
You are passing a value to a function, you are not passing a prop to a component. Props are reactive, but values are just values. You include modal in the template of comp, so rework it to take (at least) items as a prop. Then it will be reactive.
I would recommend having the remove process follow the emit-event-and-process-in-parent rather than passing a callback.
const EventBus = new Vue();
Vue.component('comp', {
data() {
return {
obj: {
a: 'foo',
b: 'bar'
}
}
},
computed: {
objKeys() {
return Object.keys(this.obj);
}
},
template: `<div>
<div>Entire object: {{ obj }}</div>
<div>Just the keys: {{ objKeys }}</div>
<button #click="remove('a')">Remove a</button>
<button #click="remove('b')">Remove b</button>
<button #click="showModal">Show Modal</button>
<modal :items="objKeys" event-name="remove" #remove="remove"></modal>
</div>`,
methods: {
remove(name) {
this.$delete(this.obj, name);
},
showModal() {
EventBus.$emit('showModal');
}
}
});
Vue.component('modal', {
props: ['items', 'eventName'],
data() {
return {
shown: false,
}
},
mounted() {
EventBus.$on('showModal', this.show);
},
template: `<div v-if="shown">
<ul v-if="items.length>0">
<li v-for="item in items">
{{ item }} <button #click="emitEvent(item)">Remove</button>
</li>
</ul>
<em v-else>empty</em>
</div>`,
methods: {
show(items, callback) {
this.shown = true;
},
emitEvent(item) {
this.$emit(this.eventName, item);
}
}
});
var app = new Vue({
el: '#app'
})
<script src="//unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
<comp></comp>
</div>

Vue: Access component object properties

I'm trying to use a statement within a element:
v-if="currentstep < maxStep"
The maxStep should be obtained from the number of components listed on my defauld export
export default {
name: 'step',
data () {
return {
maxStep: 8,
currentstep: 0
}
},
components: {
ConfigPublicador,
ConfigServico,
ModeloReceita,
Integracoes,
ConfigTema,
ConfigApp,
ConfigExtras,
Assets,
Revisao
}
}
Something like
maxStep = components.length
Any Ideias?
Thanks
This is definitely a code smell. But, you can get that value via Object.keys(this.$options.components).length.
Here's an example:
const Foo = {
template: '<div></div>',
}
new Vue({
el: '#app',
components: { Foo },
data() {
return {
count: 0,
}
},
created() {
this.count = Object.keys(this.$options.components).length;
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.4/vue.js"></script>
<div id="app">
<div v-if="count > 0">
There is at least one component
</div>
</div>

Categories

Resources