I am using vue 3 with composition api and pinia
I have an auth store that is reading from the store a default email and a default password
import { useAuthStore } from "stores/auth";
const authStore = useAuthStore();
const email = authStore.loginUser;
const password = authStore.passwordUser;
Then I am using email and password as v-model.
The problem is that both are not reactive. If I change the value from the text input, the model is not updated
I ask kindly for an explanation of the problem and a solution.
const email = authStore.loginUser
creates an email constant with the current value of authStore.loginUser, losing reactivity. To keep reactivity, you could use computed:
import { computed } from 'vue'
// ...
const email = computed({
get() { return authStore.loginUser },
set(val) { authStore.loginUser = val }
})
...or you could use the provided storeToRefs wrapper, designed for extracting/deconstructing store reactive props while keeping their reactivity (basically to avoid the above boilerplate):
import { storeToRefs } from 'pinia'
// ...
const {
loginUser: email,
passwordUser: password
} = storeToRefs(authStore)
// email & password are now reactive
Important: you only want to deconstruct state and getters using storeToRefs. Actions should be used directly from the store object (authStore in your case) or deconstructed without the wrapper:
const { actionOne, actionTwo } = authStore
This is specified in docs linked above:
... so methods and non reactive properties are completely ignored.
In conclusion, you typically end up with two deconstructions from each store:
import { useSomeStore } from '#/store'
// reactive:
const { s1, s2, g1, g2 } = storeToRefs(useSomeStore())
// non-reactive:
const { a1, a2 } = useSomeStore()
where s1, s2 are state members, g1, g2 are getters and a1, a2 are actions.
I fixed as simply as
import { useAuthStore } from "stores/auth";
const authStore = useAuthStore();
const email = ref(authStore.loginUser);
const password = ref(authStore.passwordUser);
const rememberme = ref(false);
no useless storeToRefs used
Related
I am passing a ref value to a composable function, updateableSetting. This has an initial value, settingA, but can be updated by the user. When updated, us it possible to have useFeature run again and return an updated feature value?
export default defineComponent({
setup() {
const updateableSetting = ref('settingA')
const { feature } = useFeature(updateableSetting.value)
}
})
Sure. Use a computed property
import { ref, defineComponent, computed} from 'vue'
const useFeature = (initialRef) => {
const feature = computed(() => initialRef.value + ' - I am always up to date!')
return {
feature
}
}
export default defineComponent({
setup() {
const updateableSetting = ref('settingA')
//make sure you don't pass .value here - pass the whole ref object instead
const { feature } = useFeature(updateableSetting)
return { feature }
}
})
If you mean "run a function everytime updatedValue changes", you can use
watch(
[updatedValue],
() => {console.log('updatedValue changed! doing some calculations...')}
)
I had same problem when using composables in nuxt 3.
In nuxt 3 new reactive state is available called useState. try useState instead of ref.
for more information see nuxt 3 document: useState nuxt3
I would like to know how can I show the value from composition API with v-model and Composition API.
Currently I have my store.js :
import { reactive, toRefs, computed } from "vue";
export default function users() {
// State
const state = reactive({
userForm: null,
});
// Mutations
const UPDATE_USER_FORM = (user) => {
state.userForm = user;
};
// Actions
const updateUserForm = (payload) => {
UPDATE_USER_FORM(payload);
};
// Getters
let getUserForm = computed(() => state.userForm);
return {
...toRefs(state),
updateUserForm,
getUserForm
}
}
I provide my store in createApp :
import users from '#/Stores/users';
...
let myApp = createApp({ render: () => h(app, props) });
myApp.provide('userStore', users());
I inject my store in my component :
setup(props, context) {
const userStore = inject('userStore');
return { userStore }
}
In the template I use it, but I don't see the value :
I try this :
<div>userForm : {{userStore.userForm}}</div> // see the user object
<div>userForm with value : {{userStore.userForm.value.firstname}}</div> // see the firstname value
<div>userForm no value : {{userStore.userForm.firstname}}</div> // don't see the firstname
<input v-model="userStore.userForm.firstname"> // don't see the firstname
I would like to use the value in the input...
First thing that you should do is to put the state outside the composable function in order to be available for all components as one instance :
import { reactive, toRefs, computed } from "vue";
// State
const state = reactive({
userForm: null,
});
export default function users() {
// Mutations
...
return {
state,
updateUserForm,
getUserForm
}
}
second thing is to import the composable function in any component you want since the inject/provide could have some reactivity issues :
<input v-model="state.userForm.firstname">
...
import users from './store/users'
....
setup(props, context) {
const {state,updateUserForm,getUserForm} = users();
return { state }
}
I'm using Vue 3 with the composition API and trying to understand how I can map my state from Vuex directly so the template can use it and update it on the fly with the v-model.
Does mapState works or something else to solve this issue? Right no I need to get my state by a getter, print it out in the template, and then do a manual commit for each field in my state... In Vue 2 with Vuex, I had this 100% dynamic
To make two-way binding between your input and store state you could use a writable computed property using set/get methods :
setup(){
const store=useStore()
const username=computed({
get:()=>store.getters.getUsername,
set:(newVal)=>store.dispatch('changeUsername',newVal)
})
return {username}
}
template :
<input v-model="username" />
I've solved it!
Helper function:
import { useStore } from 'vuex'
import { computed } from 'vue'
const useMapFields = (namespace, options) => {
const store = useStore()
const object = {}
if (!namespace) {
console.error('Please pass the namespace for your store.')
}
for (let x = 0; x < options.fields.length; x++) {
const field = [options.fields[x]]
object[field] = computed({
get() {
return store.state[namespace][options.base][field]
},
set(value) {
store.commit(options.mutation, { [field]: value })
}
})
}
return object
}
export default useMapFields
And in setup()
const {FIELD1, FIELD2} = useMapFields('MODULE_NAME', {
fields: [
'FIELD1',
etc…
],
base: 'form', // Deep as next level state.form
mutation: 'ModuleName/YOUR_COMMIT'
})
Vuex Mutation:
MUTATION(state, obj) {
const key = Object.keys(obj)[0]
state.form[key] = obj[key]
}
I have a store in Vuex with an empty state
const state = {
data: {
}
};
And a simple mutations for test to change value or add new data
const mutations = {
changeOrAdd(state, {key,value}) {
state.data[key] = value;
}
};
If I do a commit to changeOrAdd, it is added to state BUT it doesn't have reactivity.
I did a simple trick to change a default value
const state = {
data: {
change: 0
}
};
And in the mutation:
const mutations = {
changeValueAttr(state, {
key,
value
}) {
state.data[key] = value;
state.data.change++;
}
};
And everytime I change or add a new value, it looks like a reactivity.
But, exists a way to do this without a "default" variable and without this stupid trick?
To add a new data in store vue and make it with reactivity?
Thanks
Since your keys initially are not declared in data, Vue can't track the changes. You need to use Vue.set to reactively add properties, see change detection caveats:
import Vue from 'vue'
const mutations = {
changeOrAdd(state, {key, value}) {
Vue.set(state.data, key, value)
}
}
I'm having a Vuex getters issue where the gitters return as undefined (in the Vue Dev Console and no errors are logged in the Chrome Dev Console).
If mapGetters() is commented out (like the example code below), the returned data is displayed on screen -> Providing if the user clicks into the link that has the data. The data will NOT display if the user enters the app directly at the point where the data should display.
There is a similar question but there is no accepted answer
Vue Console Logs:
STATE:
$_lgdHRS:Object
totHrs:129
GETTERS:
$_lgdHRS/totHrs:undefined
SomeContainer.vue
<script>
import store from '../../_store'
import { mapState, mapGetters } from 'vuex'
export default {
computed: {
...mapState('$_lgdHRS',{
totHrs : 'totHrs',
}),
// ...mapGetters('$_lgdHRS',{
// totHrs : 'totHrs',
// airHrs : 'airHrs',
// picHrs : 'picHrs',
// pmcHrs : 'pmcHrs',
// voHrs : 'voHrs',
// trngHrs : 'trngHrs'
// }),
},
created() {
this.storeKey = '$_lgdHRS';
if (!(this.storeKey in this.$store._modules.root._children)) {
this.$store.registerModule(this.storeKey, store);
}
},
mounted() {
this.$store.dispatch('$_lgdHRS/getLogSummary');
},
}
</script>
<template>
<total-summary :hours="totHrs" />
</template>
state.js
export const state = {
totHrs: Number,
}
getters.js
const totHrs = state => state.totHrs;
export default {
totHrs,
};
mutations.js
const
TOTAL_HRS_UPDATED = (state, totHrs) => {
state.totHrs = +totHrs;
};
export default {
TOTAL_HRS_UPDATED,
};
Most probably because you have just displatched the request in mounted and before the data is set into the state variable your component is displayed.
Hence you can trying using async await in mounted as well as in store actions.
Do refer the following link and check the last example in this.
https://vuex.vuejs.org/guide/actions.html
The problem was that I was nesting my variables as I usually would in other frameworks.
Example:
// NESTED VARS
const r = response
totHrs = r.total,
airHrs = r.airborne,
picHrs = r.PIC,
pmcHrs = r.PMC,
voHrs = r.VO,
trngHrs = r.training;
// CHANGE TO:
const r = response
const totHrs = r.total
const airHrs = r.airborne
const picHrs = r.PIC
const pmcHrs = r.PMC
const voHrs = r.VO
const trngHrs = r.training
I don't know enough to why but your input would be greatly appreciated in the comments.