How to set initial values for data from vuex? - javascript

My goal is to create an 'edit account' form such that a user can modify their account data. I want to present the account data in a form that is already filled with the users data i.e username, email, address ...
The user can then modify the data in the form and submit this form that will update their user information.
I am using v-model to bind the form input to an object called accountInfo in my data, that looks like this:
data() {
return {
accountInfo: {
firstName: ''
}
}
}
And here is an example of a form input in my template:
<input v-model.trim="accountInfo.firstName" type="text" class="form-control" id="first-name" />
The values for the key's in the object are currently empty strings but I would like the values to come from an object called userProfile that is a state property in vuex.
In my 'edit account' component I am mapping the vuex state by importing:
import { mapState } from "vuex";
then using the following in a computed property
computed: {
...mapState(["userProfile"])
}
What I would like to do is instead of having empty strings as the values of accountInfo, assign them values from the userProfile computed property mapped from vuex, like so:
data() {
return {
accountInfo: {
firstName: this.userProfile.fristName,
}
}
}
This will provide the desired initial data for my form but unfortunately this doesn't work, presumably because data is rendered earlier on in the life cycle than computed properties.
Full code:
EditAccount.vue
<template>
<div class="container-fluid">
<form id="sign_up_form" #submit.prevent>
<div class="form-row">
<div class="form-group col-md-6">
<input v-model.trim="signupForm.firstName" type="text" class="form-control" id="first_name" />
</div>
</div>
</form>
</div>
</template>
<script>
import { mapState } from "vuex";
import SideBar from "../common/SideBar.vue";
export default {
name: "EditAccount",
computed: {
...mapState(["userProfile"])
},
data() {
return {
accountInfo: {
firstName: this.userProfile.firstName
}
};
}
};
</script>
store.js:
export const store = new Vuex.Store({
state: {
userProfile: {firstName: "Oamar", lastName: "Kanji"}
}
});

You were right, computeds are evaluated after the initial data function is called.
Quick fix
In the comments, #Jacob Goh mentioned the following:
$store should be ready before data function is called. Therefore, firstName: this.$store.state.userProfile.firstName should just work.
export default {
name: 'EditAccount',
data() {
return {
accountInfo: {
firstName: this.$store.state.userProfile.firstName
}
}
}
};
Really need computeds?
See #bottomsnap's answer, where setting the initial value can be done in the mounted lifecycle hook.
With your code, it would look like this:
import { mapState } from 'vuex';
export default {
name: 'EditAccount',
computed: {
...mapState(['userProfile'])
},
data() {
return {
accountInfo: {
firstName: ''
}
}
}
mounted() {
this.accountInfo.firstName = this.userProfile.firstName;
}
};
Though it may render once without the value, and re-render after being mounted.
Container versus presentation
I explain Vue's communication channels in another answer, but here's a simple example of what you could do.
Treat the Form component as presentation logic, so it doesn't need to know about the store, instead receiving the profile data as a prop.
export default {
props: {
profile: {
type: Object,
},
},
data() {
return {
accountInfo: {
firstName: this.profile.firstName
}
};
}
}
Then, let the parent handle the business logic, so fetching the information from the store, triggering the actions, etc.
<template>
<EditAccount :profile="userProfile" :submit="saveUserProfile"/>
</template>
<script>
import { mapState, mapActions } from "vuex";
export default {
components: { EditAccount },
computed: mapState(['userProfile']),
methods: mapActions(['saveUserProfile'])
}
</script>
While Jacob is not wrong saying that the store is ready, and that this.$store.state.userProfile.firstName will work, I feel this is more a patch around a design problem that can easily be solved with the solution above.

Bind your input with v-model as you were:
<div id="app">
<input type="text" v-model="firstName">
</div>
Use the mounted lifecycle hook to set the initial value:
import Vue from 'vue';
import { mapGetters } from 'vuex';
new Vue({
el: "#app",
data: {
firstName: null
},
computed: {
...mapGetters(["getFirstName"])
},
mounted() {
this.firstName = this.getFirstName
}
})

Related

Bind a VueX store state value to an input (VueJS)

I'm new with VueJS, and I'm creating a VueJS app where you can get some informations about a Github User,
(example: https://api.github.com/users/versifiction)
I created a store with VueX, but I need to update the value written by the user in the input,
My "inputValue" is always at "" (its default value) and when I type inside the input, the store value still at ""
I tried this :
Input.vue
<template>
<div class="input">
<input
type="text"
:placeholder="placeholder"
v-model="inputValue"
#change="setInputValue(inputValue)"
#keyup.enter="getResult(inputValue)"
/>
<input type="submit" #click="getResult(inputValue)" />
</div>
</template>
<script>
import store from "../store";
export default {
name: "Input",
props: {
placeholder: String,
},
computed: {
inputValue: () => store.state.inputValue,
},
methods: {
setInputValue: (payload) => {
store.commit("setInputValue", payload);
}
},
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped></style>
and this :
store/index.js
import Vue from "vue";
import Vuex from "vuex";
import axios from "axios";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
inputValue: "",
},
getters: {
getInputValue(state) {
return state.inputValue;
}
},
mutations: {
setInputValue(state, payload) {
console.log("setInputValue");
console.log("payload ", payload);
state.inputValue = payload;
},
},
});
According to the vuex docs in the form handling section you should do :
:value="inputValue"
#change="setInputValue"
and
methods: {
setInputValue: (event) => {
store.commit("setInputValue", event.target.value);
}
}
The simplest and elegant way to bind vuex and a component would be to use computed properties.
The above code would become,
<input
type="text"
:placeholder="placeholder"
v-model="inputValue"
#keyup.enter="getResult(inputValue)"
/>
and inside your computed properties, you'll need to replace inputValue with following code.
computed: {
inputValue: {
set(val){
this.$store.commit(‘mutationName’, val)
},
get() {
return this.$store.stateName
}
}
}

Should we use v-model to modify Vuex store?

Hello I am beginner in Vue and I do have a problem that's really bugging me.
I am wondering should we use v-model directive to modify vuex store? Vuex says that we should modify vuex store only by mutations but v-model makes everything easier and shorter.(I am asking because i couldn't find clear answer)
https://vuex.vuejs.org/guide/forms.html
When using Vuex in strict mode, it could be a bit tricky to use v-model on a piece of state that belongs to Vuex.
The "Vuex way" to deal with it is binding the <input>'s value and call an action on the input or change event.
Be sure to check out the simple "Two-way Computed Property" example on that page:
<input v-model="message">
computed: {
message: {
get () {
return this.$store.state.obj.message
},
set (value) {
this.$store.commit('updateMessage', value)
}
}
}
I think another good option which hasn't been mentioned in any answer here is to use vuex-map-fields. In fact, the library author has written a very nice explanation for the library's usefulness. As per its GitHub page, to use the library you can do something like this:
In your Vuex Store, you can have a snippet similar to this:
import Vue from 'vue';
import Vuex from 'vuex';
import { getField, updateField } from 'vuex-map-fields';
Vue.use(Vuex);
export default new Vuex.Store({
// ...
modules: {
fooModule: {
namespaced: true,
state: {
foo: '',
},
getters: {
getField,
},
mutations: {
updateField,
},
},
},
});
And in your component code, you can have something along the lines of this:
<template>
<div id="app">
<input v-model="foo">
</div>
</template>
<script>
import { mapFields } from 'vuex-map-fields';
export default {
computed: {
// `fooModule` is the name of the Vuex module.
...mapFields('fooModule', ['foo']),
},
};
</script>
Additional examples for various use cases are shown in the library's GitHub repository that I linked to in the first sentence of this answer.
Above solution can also implemented with mutations:
<template>
<input v-model="message">
</template>
<script>
import { mapMutations, mapState } from 'vuex';
export default {
computed: {
...mapState({messageFromStore: 'message'}),
message: {
get() {
return this.messageFromStore;
},
set(value) {
this.updateMessage(value);
}
}
},
methods: {
...mapMutations('updateMessage')
}
};
</script>
My Solution to this was to use a getter to set value and #input to call the mutation.
<input
type="text"
:value="$store.getters.apartmentStreet"
#input="value => $store.commit('apartmentValue', { handle: 'street', value })"
>
getters.js:
export default {
apartmentStreet: state => state.apartment.street,
};
mutations.js
export default {
apartmentValue(state, payload) {
let oldValue = state.apartment[payload.handle];
let newValue = payload.value;
if (newValue !== oldValue) state.apartment[payload.handle] = payload.value;
}
};
If you use this method be sure to check which event you want.
I use this solution.
data() {
return {
formData: {
username: '',
email: '',
bio: {
firstName: '',
lastName: ''
},
games: ['civ4', 'caesar3', 'homeworld', 'cataclysm'],
}
}
},
computed: {
...mapGetters({ //or mapState
user: 'users'
})
},
watch: {
user(newValue) {
this.formData.username = newValue.name;
this.formData.email = newValue.email;
this.formData.bio.firstName = newValue.bio.firstName;
this.formData.bio.lastName = newValue.bio.lastName;
this.formData.games = newValue.games.map(x=> { return x });
}
},
beforeCreate: fucntion() {
this.$store.dispatch('getUser');
}
And then you just regularly use v-model.
It is important to make deep copy of object from store, like using map for array, and how i did stuff with object inside.
And, also you need to have initiated this user object in store also, with empty fields.
Yes you can but is not the best practice.
As the documentation say the state should be updated only inside mutation to keep the control over the state.
But if you really want to do it you can with:
v-model="$store.state.yourProperty"

Mutating props in child component

In my parent Vue Page I'm calling a FormInput inside my form
new.vue
<b-form #submit.prevent="submit">
<FormInput :name="name"/>
<b-button #click="submit">Save</b-button>
<b-form>
<script>
import FormInput from '~/components/Insiders/FormInput';
export default {
components: {
FormInput
},
data() {
return {
name: 'User A'
}
},
methods: {
submit(event) {
console.log(this.name)
}
}
}
</script>
components/Insiders/FormInput.vue
<b-form-input v-model="name" type="text"></b-form-input>
<script>
export default {
props: {
name: { type: String, required: true }
}
}
</script>
I'm getting an error:
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: "name"
What I'm expecting here is when I change the value in the input from new.vue I should be able to console.log the new value of name when I click the submit button.
How can I solve this?
The right way for this use case is for you to support v-model for your component FormInput.
Essentially, you are building a component for user input. In that case, the component should take input prop and publish its value. Both can be done as a single configuration if you use v-model. ('Input' and 'Output' are configured with a single v-model attribute).
Refer to these articles:
https://alligator.io/vuejs/add-v-model-support/
https://scotch.io/tutorials/add-v-model-support-to-custom-vuejs-component
Edit:
v-model approach makes the FormInput component easy to use. So, the change in New.vue is simple:
<b-form #submit.prevent="submit">
<FormInput :v-model="name"/>
<b-button #click="submit">Save</b-button>
<b-form>
<script>
import FormInput from '~/components/Insiders/FormInput';
export default {
components: {
FormInput
},
data() {
return {
name: 'User A'
}
},
methods: {
submit(event) {
console.log(this.name)
}
}
}
</script>
But, the FormInput component has to still do some extra work, in order not to mutate the actual input.
<b-form-input :value="value" #input='updateVal' type="text"></b-form-input>
<script>
export default {
props: {
value: { type: String, required: true }
},
methods: {
updateVal: function(val){
this.$emit('input', val);
}
}
}
</script>

Vue: Set child-component data with props values not working

Simply, I have two components:
Parent component which passes a prop object called "profile"
Child component which receives the profile prop
The profile value is an object like this:
{
name: "Something",
email: "some#thing.com"
}
What happens?
The child component receives perfectly the profile value in the template, but it seems impossible to retrieve and set it to the component data.
What is the goal?
I want to initialise the value "email" with the profile email prop.
What did I expect?
export default {
props: ["profile"],
data() {
return {
email: this.profile.email
}
}
}
UPDATE
I haven't specified that email is a data value used as model.
I have just tried to remove it and simply print the value of email in the template and it doesn't work as well.
<!-- PARENT COMPONENT -->
<template>
<dialog-settings ref="dialogSettings" :profile="profile"></dialog-settings>
</template>
<script>
import Auth from "../services/apis/auth";
import DialogSettings from "../components/dialog-settings";
export default {
name: "app",
components: {
"dialog-settings": DialogSettings
},
beforeCreate() {
Auth.checkToken()
.then(profile => {
this.profile = profile;
})
.catch(err => {
});
},
data() {
return {
title: "App",
drawer: true,
profile: {},
navItems: []
};
}
}
</script>
<!-- CHILD COMPONENT -->
<template>
{{profile}} <!-- All the fields are passed and available (e.g. profile.email)-->
{{email}} <!-- Email is not defined -->
</template>
<script>
import Auth from "../services/apis/auth";
import DialogSettings from "../components/dialog-settings";
export default {
name: "dialog-settings",
props: ["profile"],
data() {
return {
email: this.profile.email
}
}
}
</script>
UPDATE 2
I have tried several things and I think that the problem is the asynchronous call to the API in the beforeCreate().
your child component email property should be a computed value
<!-- CHILD COMPONENT -->
<template>
<div>
{{profile}} <!-- All the fields are passed and available (e.g. profile.email)-->
{{email}} <!-- Email is not defined -->
</div>
</template>
<script>
import Auth from "../services/apis/auth";
import DialogSettings from "../components/dialog-settings";
export default {
name: "dialog-settings",
props: ["profile"],
data() {
return {
}
},
computed: {
email () {
return this.profile ? this.profile.email : 'no email yet'
}
}
}
</script>
That's because parent component property is set after rendering child component.
"Data" is not reactive, it's set once when component is created. Prop 'profile" is reactive so first when you render component you should see {} and after response from Auth is set.
If you still want to keep it in data, you could display child component like that:
<dialog-settings ref="dialogSettings" :profile="profile" v-if="profile.email"></dialog-settings>
But i wouldn't recommend that!

vuex store doesn't update component

I'm new to vue, so I'm probably making a rookie error.
I have a root vue element - raptor.js:
const Component = {
el: '#app',
store,
data: {
productList: store.state.productlist
},
beforeCreate: function () {
return store.dispatch('getProductList', 'getTrendingBrands');
},
updated: function (){
console.log(111);
startSlider();
}
};
const vm = new Vue(Component);
Using this template
<div class="grid-module-single popular-products" id="app">
<div class="row">
<div class="popular-items-slick col-xs-12">
<div v-for="product in productList">
...
</div>
</div>
</div>
My store is very simple store/index.js:
import Vue from 'vue';
import Vuex from 'vuex';
import model from '../../utilities/model';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
productlist: []
},
mutations: {
setProductList(state, data) {
state.productlist = data;
}
},
actions: {
getProductList({ commit }, action) {
return model.products().then(data => commit('setProductList', data));
}
}
});
In my vuex devtool, I can see, that the store is being updated
https://www.screencast.com/t/UGbw7JyHS3
but my component is not being updated:
https://www.screencast.com/t/KhXQrePEd
Question:
I can see from the devtools, that my code is working. The store is being updated with data. My component is not being updated,however. I thought it was enough just to add this in the data property on the component:
data: {
productList: store.state.productlist
}
but apparently the data object doesn't seem to be automatically synced with the store. So either I'm doing a complete vue no-no somewhere, or I need to tweak the code a bit. Anyway can anyone help me in the right direction.
Thanks a lot.
UPDATE
Figured it out myself. Just had to replace the components data part with a computed method:
data:
data: {
productList: store.state.productlist
}
and replace it with.
computed: {
productList () {
return store.state.productlist;
}
},
data only work once on component before render, so you can use computed instead.
like above answer, or you can use mapstate
import {mapState} from 'vuex'
...
computed: mapState({
productList: state => state.productList
})
First - use getter to do this mapGetters, also you need to watch this property somehow, you can set store subscription or just with watch method trough component.
this.$store.subscribe((mutation, state) => {
if (mutation.type === 'UPDATE_DATA') {
...
}
}
You are calling the store into the productList data property in the wrong way.
You can try it:
data: {
productList: $store.state.productlist
}
Otherwise you have to import store in each component that are using the store.

Categories

Resources