I looked at several questions to figure out what I am
doing wrong. Everything looks to me setup correctly.
GOAL
Based upon the value of COMPONENT A change hide/display content using v-show in DEPENDENT COMPONENT.
PROBLEM
Inside TextField Component, there is an input that triggers a mutation on my vuex store. Dependent Component has a computed value that listens to changes on the vuex store.
When typing in my TextField Component, I can verify by using the Vue.js extension that the mutations are triggering as expected.
HOWEVER, there is no change on the page.
COMPONENT A
<template>
<div class="field">
<input type="text" :name="field.name" v-bind:value="value" v-on:input="updateValue($event.target.value)">
</div>
</template>
<script>
export default {
props: ['field'],
methods: {
updateValue: function (value) {
this.$store.commit('UPDATE_USER_INPUT', {'key': this.field.name, 'value': value})
}
}
}
</script>
MUTATION
UPDATE_USER_INPUT (state, payload) {
state.userInput[payload['key']] = payload['value']
}
DEPENDENT COMPONENT
<template>
<div class="field-item">
{{ userInput }}
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
computed: {
...mapState([
'userInput'
])
}
}
</script>
No matter what I try, {{ userInput }} remains empty: {} until I navigate my route back to the same location. But there is no computed value listener being triggered.
If you are setting a new key within the vuex state then you will need to use:
UPDATE_USER_INPUT (state, payload) {
Vue.set(state.userInput, payload.key, payload.value)
}
Otherwise it won't be reactive.
See documentation.
Related
Tell me, please.
There is a page for changing user data. This page consists of fields for entering information about the user and a Submit button.
I need to create a popup component that performs the same functions.
The form itself will now be in a child component. The submit button is now moved to the parent component.
Therefore, I need to pass the entered data to the parent component. Everything is complicated by the fact that initially data comes from the server with previously entered information about the user. Therefore, initially you need to transfer data to the child component through props, and then, when changing them from the child, transfer it to the parent.
But what if there are a lot of variables?
For example: I need to create 15 props and then pass this data through $emit. The data itself is passed either through the #input event of the <input> tag or the #change event of the <select> tag.
I thought of three options, but I don't know which one is better. Or maybe some should not be used at all.
Using $emit
Parent component
<template>
<Child
:first-props="user.first"
:second-props="user.second"
....
:fifteenth-props="user.fifteenth"
/>
<button #click='submit'>Submit</button>
</template>
<script>
import Child from '#/components/Child.vue'
import { mapGetters } from 'vuex'
export default {
data: () => ({
first: '',
second: '',
...
fifteenth: ''
}),
components: {
Child
},
computed: {
...mapGetters({
user: 'user/getUser'
})
},
methods: {
submit() {
//Sending data
}
},
mounted: {
this.$store.dispatch('user/getUserData')
}
}
</script>
Child component
<template>
<div>
<input type="text" value="first" #input="username" />
<input type="text" value="second" #input="name" />
<input type="text" value="fifteenth" #input="surname" />
</div>
</template>
<script>
export default {
props: {
first: {
type: String,
required: true
},
first: {
type: String,
required: true
},
...
fifteenth: {
type: String,
required: true
}
},
methods: {
username() {
this.$emit('changeUsername', first)
},
name() {
this.$emit('changeName', second)
},
surname() {
this.$emit('changeSurname', fifteenth)
}
}
}
</script>
In this variant, I am worried about the number of props. I do not know if this can somehow affect the speed and quality of the component.
Using $ref
Parent component
<template>
<Child
ref='childComponent'
/>
<button click="submit">Submit</button>
</template>
<script>
import Child from '#/components/Child.vue'
export default {
data: () => ({
first: '',
second: '',
...
fifteenth: ''
}),
components: {
Child
},
method: {
submit() {
this.$refs.childComponent.submit()
}
}
}
</script>
Child component
<template>
<div>
<input type="text" v-model="first" #input="username" />
<input type="text" v-model="second" #input="name" />
<input type="text" v-model="fifteenth" #input="surname" />
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
data: () => ({
first: '',
second: '',
...
fifteenth: ''
}),
computed: {
...mapGetters({
user: 'user/getUser'
})
},
methods: {
submit() {
//Sending data
}
},
mounted: {
this.$store.dispatch('user/getUserData')
}
}
</script>
In this variant, there is not much interaction between the parent and child component. And the parent component fires only the submit method. Plus, you don't need to change the existing code too much, as you just need to move the button. But here I am concerned: is it not a bad decision to apply ref in this way ? And the second problem is the availability of data when the button is clicked. It is more likely that the child component has not yet received the necessary data or simply has not loaded yet, and the user has already pressed the button.
For example, the parent component already has a main title and a submit button. But the child component is still loading.
Although the solution here can be to make the button available for pressing only after loading the child component using :disabled="isDisabled".
Using vuex
This option immediately seems wrong to me, since the data will be used only inside this component. And vuex seems to me suitable only for global cases.
I have tried all three options and they all work. But I wanted the code not only to work, but also to be correct.
$ref and $emit don't serve the same purpose, there's generally no choice between them.
Option 1 can use a single data prop in a child:
<input
type="text"
:value="formData.first"
#input="$emit('updateFormData', { key: 'first', value: $event.target.value }))"
/>
And merge the changes in a parent:
<Child
:formData="formData"
#updateFormData="formData[$event.key] = $event.value"
/>
Option 2 is possible in its current state. Alternatively, a child can be made self-contained and contain submit button, then there is no use for $ref. It can emit submit event in case there's business logic associated with form submit that shouldn't be moved to a child.
Option 3 is possible but the use of global store for local purposes is not justified, local stores can be achieved with Vuex dynamic modules.
EDIT: Here's a repo I made for easier parsing.
I have a Component that lists products in a datatable. The first column of the table is a link that shows a modal with a form of the product that was clicked (using its ID). I'm using the PrimeVue library for styling and components.
<template>
<Column field="id" headerStyle="width: 5%">
<template #body="slotProps">
<ProductForm :product="slotProps.data" :show="showModal(slotProps.data.id)" />
<a href="#" #click.stop="toggleModal(slotProps.data.id)">
<span class="pi pi-external-link"> </span>
</a>
</template>
</Column>
</template>
<script>
import ProductForm from "./forms/ProductForm";
export default {
data() {
return {
activeModal: 0,
}
},
components: { ProductForm },
methods: {
toggleModal: function (id) {
if (this.activeModal !== 0) {
this.activeModal = 0;
return false;
}
this.activeModal = id;
},
showModal: function (id) {
return this.activeModal === id;
},
},
</script>
The modal is actually a sub component of the ProductForm component (I made a template of the Modal so I could reuse it). So it's 3 components all together (ProductList -> ProductForm -> BaseModal). Here's the product form:
<template>
<div>
<BaseModal :show="show" :header="product.name">
<span class="p-float-label">
<InputText id="name" type="text" :value="product.name" />
<label for="name">Product</label>
</span>
</BaseModal>
</div>
</template>
<script>
import BaseModal from "../_modals/BaseModal";
export default {
props: ["product", "show"],
components: { BaseModal },
data() {
return {};
},
};
</script>
When the modal pops up it uses the ProductForm subcomponent. Here is the BaseModal component:
<template>
<div>
<Dialog :header="header" :visible.sync="show" :modal="true" :closable="true" #hide="doit">
<slot />
</Dialog>
</div>
</template>
<script>
export default {
props: {
show: Boolean,
header: String,
},
methods: {
doit: function () {
let currentShow = this.show;
this.$emit("showModel", currentShow)
},
},
data() {
return {
};
},
};
</script>
I'm passing the product object, and a show boolean that designates if the modal is visible or not from the first component (ProductList) all the way down through the ProductForm component and finally to the BaseModal component. The modal is a PrimeVue component called Dialog. The component actually has it's own property called "closable" which closes the modal with an X button when clicked, that is tied to an event called hide. Everything actually works. I can open the modal and close it. For some reason I have to click the another modal link twice before it opens after the initial.
The issue is when I close a modal, I get the Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "show" error. I've tried everything to emit to the event and change the original props value there, but the error persists (even from the code above) but I'm not sure if because I'm 3 components deep it won't work. I'm pretty new to using props and slots and $emit so I know I'm doing something wrong. I'm also new to laying out components this deep so I might not even be doing the entire layout correctly. What am I missing?
Well you are emitting the showModel event from BaseModal but you are not listening for it on the parent and forwarding it+listening on grandparent (ProductForm)
But the main problem is :visible.sync="show" in BaseModal. It is same as if you do :visible="show" #update:visible="show = $event" (docs). So when the Dialog is closed, PrimeVue emits update:visible event which is picked by BaseModal component (thanks to the .sync modifier) and causes the mutation of the show prop inside BaseModal and the error message...
Remember to never use prop value directly with v-model or .sync
To fix it, use the prop indirectly via a computed with the setter:
BaseModal
<template>
<div>
<Dialog :header="header" :visible.sync="computedVisible" :modal="true" :closable="true">
<slot />
</Dialog>
</div>
</template>
<script>
export default {
props: {
show: Boolean,
header: String,
},
computed: {
computedVisible: {
get() { return this.show },
set(value) { this.$emit('update:show', value) }
}
},
};
</script>
Now you can add same computed into your ProductForm component and change the template to <BaseModal :show.sync="computedVisible" :header="product.name"> (so when the ProductForm receives the update:show event, it will emit same event to it's parent - this is required as Vue event do not "bubble up" as for example DOM events, only immediate parent component receives the event)
Final step is to handle update:show in the ProductList:
<ProductForm :product="slotProps.data" :show="showModal(slotProps.data.id)" #update:show="toggleModal(slotProps.data.id)"/>
I want to store input-value from App.vue, and use it in another component. How can I do it? I don't need the show the value in the template, I just need the value inside other components function. In JS I could just use a global var, but how can I achieve it in Vue?
App.vue:
<template>
<div id='app'>
<!-- App.vue has search bar -->
<b-form-input #keydown='search' v-model='input'></b-form-input>
<div>
<!-- Here's my other components -->
<router-view />
</div>
</div>
</template>
<script>
export default {
name: 'App',
data () {
return {
input: '',
value: ''
}
},
methods: {
search () {
this.value = this.input
this.input = ''
}
}
}
</script>
Another component:
<template>
<div>
<p>I'm another component</p>
<p>App.vue input value was: {{value}} </p>
</div>
</template>
<script>
export default {
props: ['value'],
data () {
return {
value: ''
}
}
}
</script>
This is the basic logic I'm trying to achieve. Input value in App.vue --> anotherComponent.vue
If components are not parent and child you can use store for this:
More advanced vuex store that should be your default GO TO - NPM.
Or simple solution with js object.
a. Create store.js file and export object with property in which you will store value.
b. Import store.js object to vue scripts and use it simply like:
import Store from 'store.js'
Store.value
I suffer the third day. Help me please. How in vuex.js save the text entered into the input in the "store", and then add it to the Value of the same input itself.
I'm trying to do it like this but somewhere I make a mistake.
HTML
<f7-list-input
label="Username"
name="username"
placeholder="Username"
type="text"
:value="newUserName"
#input="username = $event.target.value"
required validate
pattern="[3-9a-zA-Zа-яА-ЯёЁ]+"
v-model="saveUserName"
/>
SCRIPT
export default {
data() {
return {
username: '',
password: '',
};
},
methods: {
signIn() {
const self = this;
const app = self.$f7;
const router = self.$f7router;
router.back();
app.dialog.alert(`Username: ${self.username}<br>Password: ${self.password}`, () => {
router.back();
});
},
saveUserName(){
this.$store.commit(saveName);
}
},
computed:{
userName(){
return this.$store.state.newUserName;
}
}
};
STORE
export default new Vuex.Store({
state:{
userNameStor: 'newUserName',
userPasswordStor:''
},
mutations:{
saveName(state){
userNameStor:newUserName;
return newUserName;
}
}
});
Let's explain the whole functionnality, and then some code.
The input is in the template part of a component.
The component contain also a script part, which trigger code based on template events and so on.
The component code can trigger mutations (for state change), which are the way you store something in the store.
you have this screen to store flow:
1/ component template event => 2/ component script code => 3/ execute mutation on store
For the other side, you have this:
state => mapMutation in component computed property => component template.
Inside component script you can map a store value into a computed property of the component (with mapMutations helper). Then you map this field in your template from the component computed property.
1 - Your template
#input is the event occuring when input change by user action.
:value is the value of the input, defined programmatically.
v-model is a shorcut for using #input and :value at the same time. Don't use it with :value and #input.
Ok minimal Template:
<f7-list-input
type="text"
:value="username"
#input="changeUsername"
/>
Inside the script, you just have to link the changeUsername method to the mutation (with mapMutation), and also define a computed property whose name is username and that is a map of username from the store (with mapState).
import {mapState, mapMutations} from "vuex"
export default {
methods:{
...mapMutations({
changeUsername:"saveName"
},
computed:{
...mapState({
username:state=>state.username
}),
}
};
Consider looking at Vue doc about this mutations and state
I try to explain it as simple as possible. I have something like this. Simple Vue root, Vuex store and input with v-model inside navbar id.
That input is not reactive... Why?!
HTML
<div id="navbar">
<h2>#{{ test }}</h2>
<input v-model="test" />
</div>
store.js
import Vuex from 'vuex'
export const store = new Vuex.Store({
state: {
test: 'test'
},
getters: {
test (state) {
return state.test
}
}
})
Vue Root
import { store } from './app-store.js'
new Vue({
el: '#navbar',
store,
computed: {
test () {
return this.$store.getters.test
}
}
})
You're binding to a computed property. In order to set a value on a computed property you need to write get and set methods.
computed:{
test:{
get(){ return this.$store.getters.test; },
set( value ){ this.$store.commit("TEST_COMMIT", value );}
}
}
And in your store
mutations:{
TEST_COMMIT( state, payload ){
state.test=payload;
}
}
Now when you change the value of the input bound to test, it will trigger a commit to the store, which updates its state.
You don't want to use v-model for that. Instead, use #input="test" in your input field and in the your methods hook:
test(e){
this.$store.dispatch('setTest', e.target.value)
}
Inside your Vuex store:
In mutations:
setTest(state, payload){
state.test = payload
},
In actions:
setTest: (context,val) => {context.commit('setTest', val)},
The input should now be reactive and you should see the result in #{{test}}
Here is an example of how I handle user input with Vuex: http://codepen.io/anon/pen/gmROQq
You can easily use v-model with Vuex (with actions/mutations firing on each change) by using my library:
https://github.com/yarsky-tgz/vuex-dot
<template>
<form>
<input v-model="name"/>
<input v-model="email"/>
</form>
</template>
<script>
import { takeState } from 'vuex-dot';
export default {
computed: {
...takeState('user')
.expose(['name', 'email'])
.commit('editUser') // payload example: { name: 'Peter'}
.map()
}
}
</script>
The computed property is one-way. If you want two-way binding, make a setter as the other answers suggest or use the data property instead.
import { store } from './app-store.js'
new Vue({
el: '#navbar',
store,
// computed: {
// test() {
// return this.$store.getters.test;
// }
// }
data: function() {
return { test: this.$store.getters.test };
}
});
But a setter is better to validate input value.