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>
Related
I made two components and tried to show these in vue3 app.
my code in html
<div id="app">
<image_preview>
URL: [[image]]
</image_preview>
<file_uploader>
Counter:[[counter]]
</file_uploader>
</div>
in javascript
const ImagePreview = {
data(){
return {
image:"test.png"
}
},
mounted() {
},
delimiters: ['[[', ']]']
}
const Counter = {
data() {
return{counter: 0}
},
mounted() {
setInterval(() => {
this.counter++
}, 1000)
},
delimiters: ['[[', ']]']
}
Vue.createApp({
components:{
"image_preview":ImagePreview,
"file_uploader":Counter
}
}).mount('#app')
However nothing appears in html
Where am I wrong?
please re-read vue's documentation on components you know if you're going to need a template to render components and I bet you haven't read vue's documentation on components
follow my example and it takes care of your problem:
// </script><script type="module">
import { createApp, ref, onMounted } from 'https://unpkg.com/vue#3/dist/vue.esm-browser.js'
const ImagePreview = {
template: '#image_preview',
setup() {
return {
image: 'test.png'
}
}
}
const Counter = {
template: '#file_uploader',
setup() {
const counter = ref(0)
onMounted(() => setInterval(() => counter.value++, 1_000))
return { counter }
}
}
const app = createApp({
components:{
"image_preview": ImagePreview,
"file_uploader": Counter
}
})
.mount('#app')
<div id="app">
<image_preview>
URL: [[image]]
</image_preview>
<file_uploader>
Counter:[[counter]]
</file_uploader>
</div>
<template id="image_preview">
URL: {{ image }}
</template>
<template id="file_uploader">
Counter: {{ counter }}
</template>
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.
Variable named jso disappears when the page is refreshed. Also, is there any other way to send store information than method?
It will work when the page is refreshed and reopened without using a button.
view/userProfile.vue
<template>
<div>
<v-list>
<v-list-item>
{{userdata['username']}}</v-list-item>
<v-list-item> {{userdata['id']}}</v-list-item>
<v-list-item> {{userdata['email']}}</v-list-item>
<v-list-item> {{userdata['phone_number']}}</v-list-item>
<v-list-item> {{userdata['first_name']}} {{userdata['last_name']}}</v-list-item>
<v-list-item > {{userdata['gender']}}</v-list-item>
{{userdata['educational_status']}}
</v-list>
<hr>
{{this.profileData.gender}}
{{jso}} --> variable that disappears on page refresh
</div>
</template>
<script>
Only jso disappears on refresh page:
import Vuetify from "vuetify"
import {UserData} from "../../store/userModule";
import {JsonChoiceData} from "../../store/choiceStore";
import jsonDict from "../../jsonFiles/data.json"
import JsonFile from "../../jsonFiles/jsonfile"
export default {
name: "userProfile",
data(){
return {
profileData:{
username:'',
first_name:'',
last_name: '',
email:'',
phone_number:'',
birthday:'',
gender:'',
educational_status:'',
martial_status:'',
},
}
},
created(){
this.$store.dispatch('initUserData')
this.$store.dispatch('inijson')
},
computed:{
jso(){
return this.$store.getters.user
},
userdata (){
for(var i in this.$store.getters.getUser){
return this.$store.getters.getUser[i]
}
return this.$store.getters.getUser},
},
methods:{
getjsondata(){
console.log(this.userdata['gender'] + "methods")
this.$store.dispatch('getJsonData',this.userdata['gender'])
console.log(this.userdata['gender'])
}
},
mounted(){
this.getjsondata()
}
}
</script>
<style scoped>
</style>
store
import JsonFiles from '../jsonFiles/jsonfile'
import Jsondict from '../jsonFiles/data.json'
import jsonfile from "../jsonFiles/jsonfile";
export const JsonChoiceData = {
state: {
user: [],
},
getters: {
user(state) {
return state.user
},
},
mutations: {
inijson(state, user) {
state.user = user
},
getsonData: function (state, userinput) {
var getJsoncleandata = jsonfile.JsonData(userinput, Jsondict.Gender)
state.user = getJsoncleandata
return getJsoncleandata
}
},
actions: {
inijson(context){
context.commit('inijson', this.getsonData)
},
getJsonData(context,userinput){
context.commit('getsonData',userinput)
}
}
}
getsonData is a mutation and shouldn't be used as payload of another mutation. You are also trying to dispatch initUserData action which is not inside your store. I think that you can try to commit getsonData mutation inside your inijson action.
mutations: {
...,
getsonData: function(state, userinput) {
const getJsoncleandata = jsonfile.JsonData(userinput, Jsondict.Gender);
state.user = getJsoncleandata;
}
...
},
actions: {
inijson(context) {
context.commit('getsonData', null)
},
...
}
Then inside created hook of your component dispach only inijson action:
...
created() {
this.$store.dispatch('inijson')
},
...
If you see strange date make sure that jsonfile.JsonData(userinput, Jsondict.Gender) doesn't return a Promise.
Instead of using global $store you can also consider to use vuex store mappers. component binding helpers
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>
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.