I want to update my component's state on vuex state change - vue.js

This is my task module which I am using in store. In this my showTaskInForm() function works perfectly fine. I can see the task appear in state.task through vue dev tools
const state = {
tasks: [] // array of objects
task: null
}
const getters = {
task: state => state.task
};
const actions = {
showTaskInForm({ commit, state }, taskId) {
const task = state.tasks.filter(task => task.id === taskId);
const taskToEdit = task.length === 1 && task[0];
commit('showTaskInForm', taskToEdit);
}
};
const mutations = {
showTaskInForm: (state, taskToEdit) => (state.task = taskToEdit)
}
export default {
state,
getters,
actions,
mutations
};
And this is my AddTask.vue component
import { mapActions, mapGetters } from "vuex";
export default {
name: "AddTask",
data() {
return {
taskName : this.task ? this.task.name : null,
description : this.task ? this.task.description : null,
};
},
computed: mapGetters(['task']),
};
As I mentioned earlier I can see the state.task in vuex is being filled but in my AddTask.vue component I need to make that if there is task in vuex so my form fields get the value of the state.task.
Help me please I am new to Vue.js.

The object returned by the data function in your AddTask component is evaluated only once when the component is instantiated. That means that your expressions for taskName and description won't evaluate during the life cycle of the object.
You need to model taskName and description as computed properties in order to the tertiary operator to be evaluated after the dependency changes (in your case, the value of task coming from the store).
import { mapActions, mapGetters } from "vuex";
export default {
name: "AddTask",
computed: {
...mapGetters(['task']),
taskName() {
return this.task ? this.task.name : null
},
description() {
return this.task ? this.task.description : null
}
};

Related

Navigation component not getting re rendered with stage change in Vue3

When a user updates their username in the EditAccount component, the username is updated in the EditAccount component and in vuex store but not in the Navigation component even though stage change is updated to the new user name.
The problem is that the user is seing thier old user name in Navigation component and a updated user name in the EditAccount component and they don't match.
How can I Re render the Navigation component with the new user name?
Below is the the code for user the data in the Navigation component.
Store vuex: index.js
const store = createStore({
// strict: true,
state: {
user: null,
authIsReady: false,
//
// current category
playlistCategory: null,
},
//
getters: {
getUser(state) {
return state.user;
},
},
mutations: {
//
// update playlist category
updatePlaylistCategory(state, payload) {
state.playlistCategory = payload;
},
//
//
setUser(state, payload) {
state.user = payload;
},
//
setAuthIsReady(state, payload) {
state.authIsReady = payload;
},
//
},
actions: {
async editUser(context, payload) {
const { displayNewName, displayNewEmail } = payload;
await updateUserDetails(displayNewName, displayNewEmail);
// get current user
const responseUser = await user;
// set user state
context.commit('setUser', responseUser);
},
},
NavBar.vue
// vue3 and composition api
setup() {
// store
const store = useStore()
//
const { error, logout, isPending } = useLogout()
const router = useRouter()
//
// getters
const user = computed(() => {
return store.getters.getUser.displayName
})
Try adding set and get property:
const user = computed({
get: store.state.user,
set: (val) => store.state.user = val
});
Try using a getter instead acessing the value directly in the state
Getter for user:
export function getUser(state){
return state.getUser
}
and in the component import the getter like this:
<script>
import {mapGetters} from 'vuex'
export default {
computed: {
...mapGetters('*theStoreName*',['getUser'])
},
watch: {
getUser: function(){
//Should be possible to see when the getUser changes here
console.log(this.getUser)
}
}
}
</script>
Note: You have theStoreName for the store name you're using
Maybe the problem is that the store name is missing, or when you did store.state.user you're acessing the store? If it is it, then you should try to inform the variable you're trying to access, like If it is, like store.state.user.name, with the getter it would be: getUser.name

v-model and Composition API with provide and inject

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 }
}

VueX: Don't nest state with modules?

So, I love the idea of VueX modules and separating my data out, as it makes it far easier to reason when there are large sets of data... but I hate having to refer to them as nested objects in the store's state.
This is how the module currently works:
contactData.js:
export const contactData = {
state: {
contactInfo: null,
hasExpiredContacts: false
},
mutations: {
updateContactInfo(state, data) {
state.contactInfo = data;
},
updateExpired(state, data) {
state.hasExpiredContacts = data;
}
}
}
store.js:
import Vue from 'vue';
import Vuex from 'vuex';
import { contactData } from './contactData.js';
Vue.use(Vuex);
export default new Vuex.Store({
modules: { contactData },
state: {
otherData: null
}
});
Which would return as:
store: {
state: {
contactData: {
contactInfo: null,
hasExpiredContacts: false
},
otherData: null
}
}
Is there anyway to, instead, display it as the following, while still using a module?
store: {
state: {
contactInfo: null,
hasExpiredContacts: false,
otherData: null
}
}
I'm not sure that flattening out all your state would necessarily be a great idea if the project grew larger, as you'd have to be wary of property name clashes.
However, ignoring that, you could perhaps create flat getters for all module state automatically. Since this just provides alternative access all actions and mutations will work in the normal way.
const modules = {
contactData,
user,
search,
...
}
const flatStateGetters = (modules) => {
const result = {}
Object.keys(modules).forEach(moduleName => {
const moduleGetters = Object.keys(modules[moduleName].getters || {});
Object.keys(modules[moduleName].state).forEach(propName => {
if (!moduleGetters.includes(propName)) {
result[propName] = (state) => state[moduleName][propName];
}
})
})
return result;
}
export const store = new Vuex.Store({
modules,
getters: flatStateGetters(modules),
state: {
otherData: null
}
})
Since there's no deep merge possible still in ES6/ES7, you can't do it like the way you want.
You need to make your own function or find a suitable library to deep merge the objects to make it work.
Here's a possible solution using lodash:
modules: { _.merge(contactData, { state: { otherData: null } } ) }

Value of vuex getter not updating when switching between routed components

I have a vuex getter that takes the value of an array from my state.
import Vue from 'vue'
import Vuex from 'vuex';
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
tableRow: [
{
"name": 1,
"numSongs": 2,
"Year": 3
}
]
},
getters:{
tRow : state => {
return state.tableRow
}
}
});
I have a function that takes the values from a table row and sets the value of it to my computed property for the getter.
$("#table1 tr").click(function () {
var list = [];
var $row = $(this).closest("tr"),
$tds = $row.find("td");
list.push({ name: $tds.eq(0).text(), numSongs: $tds.eq(1).text(), Year: $tds.eq(2).text() });
// self.$store.state.tableRow = list;
this.tabRow = list;
console.log(this.tabRow);
self.passData();
});
});
},
computed: {
tabRow(){
return this.$store.getters.tRow;
}
},
Then in another one of my routed components i set the same computed property and then try to call it in the template but the value it outputs is the default values i gave it.
mounted: function () {
var self = this;
console.log(this.tabRow)
// self.passedRow = self.$store.state.tableRow;
self.passedRow = this.tabRow;
this.startDoughnut(this.$refs.canvas, 'Doughnut');
},
computed: {
tabRow(){
return this.$store.getters.tRow;
}
I am not properly efficient with vuex yet so i am not sure why this won't work any help would be appreciated.
What you are trying to do here is not possible in vue compute property and also in Vuex. Computed properties and vuex getters areread only.
this.tabRow = list;
======================
computed: {
tabRow(){
return this.$store.getters.tRow;
}
Do this
computed: {
tabRow(){
get: function () {
return this.$store.getters.tRow;
},
set: function (newValue) {
commit('SET_TABLEROW', newValue)
});
}
}
In store add a mutation
mutations:{
SET_TABLEROW: (state,list) => {
state.tableRow=list
}
}
refer https://vuex.vuejs.org/en/mutations.html
I have a function that takes the values from a table row and sets the value of it to my computed property for the getter.
Examining your second block of code:
You are trying to set the value of list to the computed property tabRow. Computed properties are by default getter-only, i.e we can only get or acess a value not set a value.
As far as I understood your problem you want to take the value from the table row and add it to the tableRow property in your vuex state. For that you need a mutation
$("#table1 tr").click(function () {
var $row = $(this).closest("tr"),
$tds = $row.find("td");
var list = { name: $tds.eq(0).text(), numSongs: $tds.eq(1).text(), Year: $tds.eq(2).text() };
self.$store.commit('addTableRow', list);
self.passData();
});
In you vuex store add the mutation
import Vue from 'vue'
import Vuex from 'vuex';
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
tableRow: [
{
"name": 1,
"numSongs": 2,
"Year": 3
}
]
},
getters:{
tRow : state => {
return state.tableRow
}
},
mutations: {
addTableRow: (state, list) => {
state.tableRow.push(list);
}
}
});

Pass params to mapGetters

I use vuex and mapGetters helper in my component. I got this function:
getProductGroup(productIndex) {
return this.$store.getters['products/findProductGroup'](productIndex)
}
Is it possible to move this somehow to mapGetters? The problem is that I also pass an argument to the function, so I couldn't find a way to put this in mapGetters
If your getter takes in a parameter like this:
getters: {
foo(state) {
return (bar) => {
return bar;
}
}
}
Then you can map the getter directly:
computed: {
...mapGetters(['foo'])
}
And just pass in the parameter to this.foo:
mounted() {
console.log(this.foo('hello')); // logs "hello"
}
Sorry, I'm with #Golinmarq on this one.
For anyone looking for a solution to this where you don't need to execute your computed properties in your template you wont get it out of the box.
https://github.com/vuejs/vuex/blob/dev/src/helpers.js#L64
Here's a little snippet I've used to curry the mappedGetters with additional arguments. This presumes your getter returns a function that takes your additional arguments but you could quite easily retrofit it so the getter takes both the state and the additional arguments.
import Vue from "vue";
import Vuex, { mapGetters } from "vuex";
Vue.use(Vuex);
const store = new Vuex.Store({
modules: {
myModule: {
state: {
items: [],
},
actions: {
getItem: state => index => state.items[index]
}
},
}
});
const curryMapGetters = args => (namespace, getters) =>
Object.entries(mapGetters(namespace, getters)).reduce(
(acc, [getter, fn]) => ({
...acc,
[getter]: state =>
fn.call(state)(...(Array.isArray(args) ? args : [args]))
}),
{}
);
export default {
store,
name: 'example',
computed: {
...curryMapGetters(0)('myModule', ["getItem"])
}
};
Gist is here https://gist.github.com/stwilz/8bcba580cc5b927d7993cddb5dfb4cb1