Ensure multiple instances of the same component have non-shared state - javascript

I have an app in which I have a counter which shows a value, and a button that can increment that value.
I used simple state management from scratch as the docs suggest.
I can add counters to this list with an 'Add Counter' button so that I have multiple counters on the page.
Despite each instance of my counter component having a separate key in the parent component (as per the docs), each instance of the counter shares the same value:
How can I add a separate instance of the same component that has its own state?
Here is the code on webpackbin: http://www.webpackbin.com/41hjaNLXM
Code:
App.vue
<template>
<div id="app">
<counter v-for="n in state.countersAmount" :key="n"></counter>
<button v-on:click="addCounter">Add a Counter</button>
</div>
</template>
<script>
import Counter from './Counter.vue'
const store = {
state: {
countersAmount: 1
},
incrementCounters() {
++this.state.countersAmount
}
}
export default {
data() {
return {
state: store.state
}
},
methods: {
addCounter() {
store.incrementCounters()
}
},
components: {
Counter
}
}
</script>
Counter.vue
<template>
<div>
<h1>{{state.counterValue}}</h1>
<button v-on:click="increment">+</button>
</div>
</template>
<script>
const store = {
state: {
counterValue: 0,
},
increment() {
++this.state.counterValue
}
}
export default {
data() {
return {
state: store.state
}
},
methods: {
increment() {
store.increment()
}
}
}
</script>

You're using the same state for every Counter instance.
const store = {
state: {
counterValue: 0,
},
increment() {
++this.state.counterValue
}
}
The code above will be executed only once, and every instance of this component will share this state.
To change this, just return a new object as the initial state, like so:
<template>
<div>
<h1>{{counterValue}}</h1>
<button v-on:click="increment">+</button>
</div>
</template>
<script>
export default {
data() {
return {
counterValue: 0
}
},
methods: {
increment() {
++this.counterValue;
}
}
}
</script>
The Simple State Management from Scratch you linked, is for shared state between components, as you can see in the picture:

You are always returning the same instance of the component. Instead, you should return a new instance.

Related

Vue JS - Problem with incrementing value by one in parent component using $emit

I am learning $emit in Vue JS, I decided to create a value called counter in the child component then increment by one when the button is clicked, but I decided to write all the logic in the parent component using $emit
But every time I click on the button, the value does not increase although the method works
LifeCycles.vue
<template>
<div>
<p>{{counter}}</p>
<button #click="add">Click me</button>
</div>
</template>
<script>
export default {
data() {
return {
counter: 0,
};
},
methods: {
add() {
this.$emit('updated', this.counter)
}
},
};
</script>
HeadlineLifeCycle.vue
<template>
<div>
<LifeCycles #updated="usefulMethod" />
</div>
</template>
<script>
import LifeCycles from "./LifeCycles.vue";
export default {
components: {
LifeCycles,
},
methods: {
usefulMethod: function(counter) {
console.log(counter++)
}
}
};
</script>
This is because you're not incrementing count in the child component where it's placed in its data:
const lifecycles = Vue.component('lifecycles', {
template: "#lifecycles",
data() { return { counter: 0 } },
methods: {
add() { this.$emit('updated', ++this.counter); } // fix
}
});
new Vue({
el: "#headlinelifecycle",
components: { lifecycles },
methods: {
usefulMethod: function(counter) { console.log(counter); }
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<template id="lifecycles">
<div>
<p>{{counter}}</p>
<button #click="add">Click me</button>
</div>
</template>
<div id="headlinelifecycle"><LifeCycles #updated="usefulMethod" /></div>
If you want to control LifeCycles from the parent state:
const lifecycles = Vue.component('lifecycles', {
template: "#lifecycles",
props: ['counter'],
methods: {
add() { this.$emit('updated', this.counter+1); }
}
});
new Vue({
el: "#headlinelifecycle",
components: { lifecycles },
data() { return { counter: 0 } },
methods: {
usefulMethod: function(counter) {
this.counter = counter;
console.log(this.counter);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<template id="lifecycles">
<div>
<p>{{counter}}</p>
<button #click="add">Click me</button>
</div>
</template>
<div id="headlinelifecycle">
<LifeCycles #updated="usefulMethod" :counter="counter" />
</div>
The line
this.$emit('updated', this.counter)
is not emitting this.counter reference, so any time you click on the button, you actually are emmiting the counter initial value which is 0.
So, if you really need to do all the logic in the parent component, you have to define the counter variable in parent component:
lifecycle.vue
<template>
<div>
<p>{{counter}}</p>
<button #click="$emit('clicked')">Click me</button>
</div>
</template>
HeadlineLifeCycle.vue
<template>
<div>
<LifeCycles #clicked="increase" />
</div>
</template>
<script>
import LifeCycles from "./LifeCycles.vue";
export default {
components: {
LifeCycles,
},
data() {
return {
counter: 0,
};
},
methods: {
increase: function() {
console.log(this.counter++)
}
}
};
</script>
You also can pass a variable to the lifecycle.vue as model and manage its value in lifecycle component.

Vue component using computed property of the mixin

I have a simple component that uses mixin that's shared across multiple components with similar functionality.
When I run it I seem to be getting
Property or method "activeClass" is not defined on the instance but
referenced during render.
Here's my mixin
<script>
export default {
data() {
return {
opened: false,
identity: ''
}
},
computed: {
activeClass() {
return {
active: this.opened
};
}
},
created() {
window.EventHandler.listen(this.identity + '-toggled', opened => this.opened = opened);
},
methods: {
toggle() {
window.EventHandler.fire('toggle-' + this.identity);
}
}
}
</script>
and my component
<template>
<span class="pointer" :class="activeClass" #click="toggle"><i class="fas fa-search"></i></span>
</template>
<script>
import Trigger from '../../mixins/Trigger';
export default {
data() {
return {
mixins: [Trigger],
data() {
return {
identity: 'language'
}
}
}
}
}
</script>
For some reason I cannot seem to be able to access activeClass computed property from within the component. Any idea why is this happening?
Try to move mixin to components main scope. Not in data function rerurn

Getter should be a function but "getters.doubleCounter" is 20 - VUEX ERROR

I'm learning about Vuex right now and I'm running into some trouble. While trying to create a getter on my vuex instance I'm getting this error when trying to render from one of my components:
Getter should be a function but "getters.doubleCounter" is 20
store.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
counter: 10
},
getters: {
doubleCounter: state => {
return state.counter * 2;
}
}
});
MY COMPONENT:
<template>
<div>
<p>This is a message from services</p>
<button v-on:click="increment">+</button>
<button v-on:click="decrement">-</button>
{{ counter }}
</div>
</template>
<script>
export default {
computed: {
counter() {
return this.$store.getters.doubleCounter;
},
},
methods: {
increment: function () {
this.$store.state.counter++
},
decrement: function () {
this.$store.state.counter--
}
}
}
</script>
Again when I try to render the page that this component is on. It fails while giving me the title error message in the console. Any help would be great! Thanks!
Try this.
doubleCounter: (state) => {
return state.counter * 2;
}
I'm not sure what's causing your error but you are certainly not meant to be directly manipulating state outside of a mutation.
At no point should your code ever assign anything directly to a state property. For example, this is bad
this.$store.state.doubleCounter++
Here's what you should have.
Vue.component('counter', {
template: `
<div>
<p>This is a message from services</p>
<button v-on:click="increment">+</button>
<button v-on:click="decrement">-</button>
{{ counter }}
</div>
`,
computed: {
counter() {
return this.$store.getters.doubleCounter;
},
},
methods: {
increment: function () {
this.$store.commit('increment')
},
decrement: function () {
this.$store.commit('decrement')
}
}
})
const store = new Vuex.Store({
state: {
counter: 10
},
mutations: {
increment(state) { state.counter++ },
decrement(state) { state.counter-- }
},
getters: {
doubleCounter: state => {
return state.counter * 2;
}
}
})
new Vue({
el: '#app',
store
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.13/dist/vue.js"></script>
<script src="https://unpkg.com/vuex#3.0.1/dist/vuex.js"></script>
<div id="app"><counter/></div>

vue js props reset a timer

<template>
<span>{{time}}</span>
</template>
<script>
export default {
name: 'Timer',
data () {
return {
time: 0
}
},
mounted () {
setInterval(() => {
++this.time
}, 1000)
}
}
</script>
The parent
<template>
<div class="tetris">
<timer></timer>
</div>
</template>
<script>
import Timer from './Timer'
export default {
name: 'Game',
components: {
Timer: Timer
},
}
What is the best way to reset the timer here from the parent?
I want to avoid emitting events as this would result in all uses of the timer being reset. Is the best option to pass in a prop eg "start" "stop" "reset"?
Using a prop and watching it could be a good way to handle this if you're hoping to propagate events from the parent to the child. In general, communication of data between parent and child components should be done with props for parent-to-child communication and emitting events for child-to-parent communication.
There are, of course, additional libraries available for managing inter-component communication, but the above generally holds true for pure Vue.js code.
Yes there is a good way instead of increasing the timer in the child make it in the parent data and increment it in the parent and use props to pass it to the child and you can reset it in the parent when you like >> you can copy the code below and see the results
child code
<template>
<span>{{timer}}</span>
</template>
<script>
export default {
name: 'Timer',
data () {
return {
}
},
props:{timer:Number},
mounted() {
}
}
</script>
parent code
<script>
import Timer from './Timer'
export default {
name: 'Game',
data(){
return {
timerInit:0
},
components: {
Timer: Timer
},
mounted(){
setInterval(()=>{
++this.timerInit ;
console.log(this.timerInit);
},1000)
},
methods:{
clickHandler(){
this.timerInit = 0 ;
console.log(this.timerInit);
}
}
}
<template>
<div class="tetris">
<timer:timer="timerInit"/>
<button #click="clickHandler">reset</button>
</div>
</template>

Mount parent component in child

I have two files named Recursive.vue and Value.vue.
In the first instance Recursive is the parent. Mounting Recursive in Recursive goes great, same for mounting Value in Recursive and after that Value in Value.
But when I've mounted Value in Recursive and trying to mount Recursive in Value after that I get the following error:
[Vue warn]: Failed to mount component: template or render function not defined.
(found in component <recursive>)
How can I make my problem work?
This is what my files are looking like:
Recursive
<template>
<div class="recursive">
<h1 #click="toggle">{{comps}}</h1>
<div v-if="isEven">
Hello
<value :comps="comps"></value>
</div>
</div>
</template>
<script>
import Value from './Value.vue'
export default {
name: 'recursive',
components: {
Value
},
props: {
comps: Number
},
computed: {
isEven () {
return this.comps % 2 == 0;
}
},
methods: {
toggle () {
this.comps++;
}
}
}
</script>
Value
<template>
<div class="value">
<h1 #click="toggle">{{comps}}</h1>
<div v-if="isEven">
<recursive :comps="comps"></recursive>
</div>
</div>
</template>
<script>
import Recursive from './Recursive.vue'
export default {
name: 'value',
components: {
Recursive
},
props: {
comps: Number
},
computed: {
isEven () {
return this.comps % 2 == 0;
}
},
methods: {
toggle () {
this.comps++;
}
}
}
</script>
Mounter
<template>
<div class="mounter">
<h1>HI</h1>
<recursive :comps="comps"></recursive>
</div>
</template>
<script>
import Recursive from './Recursive'
export default {
name: 'mounter',
components: {
Recursive
},
data () {
return {
comps: 0
}
}
}
</script>
I had a similar problem before. The only way out was declaring the component as "global", because importing it in the component which actually required it never worked.
new Vue({
...
})
Vue.component('recursive', require('./Recursive'))
Then you can just use without importing:
// Mounted
<template>
<div class="mounter">
<h1>HI</h1>
<recursive :comps="comps"></recursive>
</div>
</template>
<script>
export default {
name: 'mounter',
data () {
return {
comps: 0
}
}
}
</script>

Categories