How to update Vuex store correctly? - javascript

I'm working on the time-honored Todo list feature using Vue, Vuex, and axios. I have the feature componentized between the
whole list itself (TodoList) and the individual rows (TodoRow).
Please note that I have abbreviated the code snippets below to eliminate properties and elements that are not essential to understanding this problem. I would not recommend running the code locally.
TodoList component
<template>
<dashboard-container
:header-title="'To-Do-List'"
>
<div slot="action-buttons">
<add-new-button
v-if="!emptyTodos"
:disabled="isCreating"
class="pull-right"
#click="addNewTodo"
/>
</div>
<transition slot="container-content" tag="div">
<div v-if="!emptyTodos">
{{ todos }}
<todo-row
v-for="(item, index) in todos"
:key="index" :id="item.id"
:text="item.description"
:status="item.status"
:completed="item.completed"
/>
</div>
<empty-list v-else #addClick="addNewTodo" />
</transition>
</dashboard-container>
</template>
<script>
import DashboardContainer from '#/components/Dashboards/DashboardContainer'
import TodoRow from '#/components/TodoList/TodoRow'
import AddNewButton from './AddNewButton'
import EmptyList from './EmptyList'
import { mapState, mapActions } from 'vuex'
export default {
components: {
DashboardContainer,
EmptyList,
TodoRow,
AddNewButton
},
computed: {
...mapState('planProfile', {
todos: 'todos'
}),
emptyTodos () {
return this.todos.length === 0
},
isCreating () {
return this.todos.some(todo => todo.status === 'created')
}
},
mounted () {
return this.dispatchAction({
actionName: 'planProfile/getTodos'
}).catch(err => {
console.log('decide how to route handle errors', err)
})
},
methods: {
...mapActions({
dispatchAction: 'dispatchAction'
}),
addNewTodo () {
this.todos.unshift({
text: '',
done: false,
status: 'new'
})
}
}
}
</script>
Here is what the feature (with the non-abbreviated code) looks like in my browser with a few todo items. I have dumped the todos just for
easy illustration of the raw data in the store.
Here is the abbreviated code for the todo item itself:
TodoItem component
<template>
<div #mouseover="showActions = true" #mouseout="showActions = false">
<div>
<div>
<label>
<input
:checked="done"
type="checkbox"
>
</label>
</div>
<div v-show="showActions">
<div
v-for="action in currentActions"
:key="action.name"
class="action-icon pull-right"
#click="handleActionClick(action.name)"
>
<svg-icon :svg-settings="action.icon" />
</div>
</div>
</div>
</div>
</template>
<script>
import makeMdsIcon from '#/utils/makeMdsIcon'
import CheckboxInput from '#/components/Inputs/CheckboxInput'
import { mapActions } from 'vuex'
export default {
components: {
CheckboxInput
},
props: {
id: {
type: Number,
default: null
},
status: {
type: String,
default: 'created'
},
completed: {
type: Boolean,
default: false
}
},
data () {
return {
showActions: false,
checkIcon: makeMdsIcon('check--s')
}
},
computed: {
done () {
return this.completed
},
isCreating () {
return this.status === 'new' || this.status === 'updating'
},
currentActions () {
const actions = {
new: [{
name: 'save',
icon: this.checkIcon
}]
}
return actions[this.status]
}
},
created () {
if (this.completed) {
this.status = 'done'
}
},
methods: {
...mapActions({
dispatchAction: 'dispatchAction'
}),
saveTodo () {
this.status = 'created'
return this.dispatchAction({
actionName: 'planProfile/createTodo',
data: {
description: this.text
}
}).catch(err => {
console.log('decide how to route handle errors', err)
})
},
handleActionClick (actionName) {
const handlers = {
save: this.saveTodo
}
const currentHandler = handlers[actionName]
return currentHandler()
}
}
}
</script>
When the saveTodo method is called and the underlying planProfile/updateStatusTodo action is called at the Vuex store level, the save persists on the backend and I can verify this when I refresh. However, as we can see from the TodoList component, the store is not updated when this happens:
Abbreviated Vuex store
import todoService from '#/services/todoService'
const state = {
todos: []
}
const actions = {
createTodo: {
handler ({ commit }, payload = []) {
const { description } = payload
return todoService.createTodo({ description })
.then(todoData => {
return commit('addTodo', todoData)
})
}
}
}
const mutations = {
addTodo (state, payload) {
/* The line below is just for demonstrative purposes for this question */
state.todos = state.todos.push({id: 9999, description: 'this is mock data for SO', completed: false})
}
}
export default {
namespaced: true,
state,
actions,
mutations
}
Why is the store not being updated when addTodo is called at the Vuex store level? I have already validated the procedure
for other todo operations (such as deleting a todo) whereby I delete the todo and this information is instantaneously communicated to the
TodoList component, why is the same not happening for creating new todos?
Flow:
Right before I am about to save via clicking on the checkmark next to the first item:
After clicking, item has been saved on the backend (which I can verify by refreshing), but it's not in the store since the todos have not changed (and also by inspection of the Vuex store in the Chrome developer console).

Related

Vuex state not updating with mutation

I'm trying to change the value of a state from false to true but it's not seeming to work. Below I will add my store, component that only gets the state, and the component that should update the state.
//Store
export const store = new Vuex.Store({
state: {
newComment: false,
status: ''
},
mutations: {
updateComments(state, payload) {
state.newComment = payload;
}
}
});
// Component that gets state
<template>
<div class="home">
<h1>{{newComment}}</h1>
<SelectedVideo v-bind:user="user" v-bind:video="videos[0]"/>
</div>
</template>
<script>
import axios from 'axios';
import SelectedVideo from '../components/SelectedVideo.component';
axios.defaults.withCredentials = true;
export default {
name: 'Home',
components: {
SelectedVideo
},
data() {
return {
videos: [],
user: null
}
},
computed: {
newComment() {
return this.$store.state.newComment
}
},
// Component I need to update state
methods: {
updateComments() {
this.$store.dispatch('updateComments', true);
}
},
async createComment(comment, video, user) {
try{
const res = await axios({
method: 'POST',
url: 'http://localhost:8000/api/v1/comments/',
data: {
comment,
video,
user
}
});
if (res.data.status === 'success') {
console.log(res);
// location.reload(true);
this.updateComments();
}
} catch(err) {
I'm successfully getting the state but updating seems to have no affect on state when I try to invoke the function to update.
First make a getter for using state data.
getters:{
get_newComment: state => state.newComment
}
Than implement this into your computed property.
computed: {
newComment() {
return this.$store.getters.get_newComment
}
}
After then use to "Commit" to commit some changes not "dispatch". Dispatch using for calling actions.

'unknown action type' with Vuex mapAction

I am new to Vuex and trying to use mapActions to fetch some data from my store, and send it into my component's template. I keep getting the error message
[vuex] unknown action type: getItemDetail but I don't know why.
In my store, the action I'm trying to dispatch is getItemDetail. My full store is
import fetch from './fetch';
const url = 'items';
const defaults = {
id: '',
rating: '',
type: '',
};
const state = { ...defaults, };
const getters = {};
const actions = {
getItemDetail ({ commit, }, itemId) {
fetch.get(`${url}/${itemId}`).then((response) => {
commit('setItemDetail', { ...defaults, ...response.data, });
});
},
};
const mutations = {
setItemDetail (state, item) {
state.item = item;
},
};
export default {
namespaced: true,
state,
getters,
actions,
mutations,
};
In my component I have:
<template>
<div>
<p> {{ itemDetail }} </p>
</div>
</template>
<script>
import { mapActions } from 'vuex';
export default {
computed: {
itemDetail () {
this.getItemDetail('23451');
return this.$store.state;
},
},
methods: {
...mapActions([
'getItemDetail',
]),
},
};
</script>
Any help would be much appreciated!!!
From what I see of your code, you are in a namespaced store module.
To access a namespaced action, you need to map it with the name of the store module as the first parameter (the same should be applied for any mapState or mapGetter) :
methods: {
...mapActions("yourModuleName", [
'getItemDetail',
]),
},

Vuex getter wasn't updated after store mutation

I've created app using VueJS and Vuex.
This code is used in store:
const separateArticle = {
state: {article: {}},
mutations: {
LOAD_ARTICLE(state, id) {
fetch(`http://localhost:3000/articles/${id}`, { mode: "cors" })
.then(resp => resp.json())
.then(response => {
state.article = response;})}
},
actions: {
loadSeparateArticle({ commit }, id) {commit("LOAD_ARTICLE", id);}
},
getters: { articleData(state) {return state.article;} }
};
component code :
<template>
<div>
<img src="articleData.imgSrc"/>
<Article v-bind:article = "articleData" v-bind:key="articleData._id"></Article>
<div>
Comments
</div>
</div>
</template>
<script>
import Article from "./Article.vue";
export default {
props: ["article"],
name: "ArticleDetail",
components: {
Article
},
data() {
return { articleData: this.$store.getters.articleData };
},
created() {
this.$store.dispatch("loadSeparateArticle", this.$route.params._id);
}
};
</script>
When the component is created getter is triggered, but after store modification, it wasn't called. Vue doesn't understand that store is updated.
How can I manage this issue?

Pass a state of vuex to a chart - vue

I'm doing a chart component that will be several times represented on a page.
For that, I'm using Vuex where I have the mutations / actions to get that date, and everything works fine.
In the graph component, in created () I call the action to make the request, and I want to pass the date to the chart.
The state comes as follows -
{
typeChart: [data],
typeChart2: [data2]
}
If I do console.log from the state it appears fine but if using the key returns undefined.
console.log(this.dataChart)
// log - { stacked: Array }
console.log(this.dataChart.stacked);
// log - undefined
The only way I found to pass the state to the chart was to do a timeout but I think that is not the best way to do this.
here's the code I have
component of page -
<template>
<stacked-chart
endpoint="endpoint"
chart="stacked"
:states="['state1', 'state2', 'state3']"
:keys="['key1', 'key2', 'key3']"
/>
</template>
component of chart -
<template>
<div class="box-content-white">
<div class="title"> Gráfico 1 </div> <!-- //! name for chart -->
<column-chart :data="data" :stacked="true" :colors="['#006837', '#FF0000', '#000']"></column-chart>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import { mapActions } from 'vuex';
export default {
props: {
endpoint: String,
chart: String,
states: Array,
keys: Array
},
data() {
return {
data: []
}
},
created() {
this.fetchReporting({endpoint: this.endpoint, chart: this.chart, states: this.states, keys: this.keys, formData: { state: 'negotiation' }});
console.log(this.dataChart);
console.log(this.dataChart[this.chart]);
setTimeout(() =>{
this.data = this.dataChart[this.chart];
}, 150);
},
methods: {
...mapActions({
fetchReporting: 'fetchReporting'
})
},
mounted() {
this.data = this.dataChart[this.chart];
},
computed: {
...mapGetters({
dataChart: 'dataChart'
})
},
watch: {
}
}
</script>
file with vuex -
import axios from 'axios';
const state = {
dataChart: {}
}
const mutations = {
'ADD_DATA_CHART'(state, data) {
state.dataChart[data.key] = [];
[].forEach.call(data.states, (s, i) => {
let obj = {};
obj.name = s;
obj.data = [];
[].forEach.call(data.value, d => {
obj.data.push([d.name, d[data.keys[i]].toFixed(2)]);
});
state.dataChart[data.key].push(obj);
});
}
}
const actions = {
fetchReporting({state, commit}, response) {
axios.post(response.endpoint, response.formData)
.then(({data}) => {
commit('ADD_DATA_CHART', {key: response.chart, value: data, states: response.states, keys: response.keys})
}).catch(err => {
console.log(err);
});
}
}
const getters = {
dataChart: state => {
return state.dataChart;
}
}
export default {
state,
mutations,
actions,
getters
}
any suggestion?
You need to wait for you action fetchReporting to complete. Either by using async/await or by using promise/resolve

Vuejs and Vuex actions - Request failed with status code 422

I'm creating a system comment with Laravel 5.5, Vue 2 and Vuex.
I Can't post a comment. I get in my console, two errors:
TypeError: this.addComment is not a function
Error: Request failed with status code 422
This is my code:
import { addComment } from '../../store/actions'
export default {
computed: {
addComment
},
vuex: {
actions: { addComment }
},
data () {...},
methods: {
sendComment: function () {
this.addComment({
commentable_id: this.id,
commentable_type: this.model,
content: this.content,
reply: this.reply
})
}
actions.js code
export const addComment = function ({dispatch}, comment) {
return axios.post('/comments', comment).then((response) => {
dispatch('ADD_COMMENT', response.data)
})
};
All my routes, controller and mutations are tested and work well.
You don't need to import actions into your components as long as the store is registered globally. So you simply need to call addComment like this:
this.$store.dispatch('addComment', {
commentable_id: this.id,
commentable_type: this.model,
content: this.content,
reply: this.reply
})
Also, putting addComment in computed doesn't make sense so you have to remove it.
In your addComment action, I believe it's called commit not dispatch:
export const addComment = function ({commit}, comment) {
return axios.post('/comments', comment).then((response) => {
commit('ADD_COMMENT', response.data)
})
}
My Store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
export const state = {
comments: []
};
export const mutations = {
ADD_COMMENTS (state, comments) {
state.comments.push(...comments)
},
ADD_COMMENT (state, comment) {
if (comment.reply) {
let c = state.comments.find((c) => c.id === comment.reply);
if (c.replies === undefined) {
c.replies = []
}
c.replies.push(comment)
} else {
state.comments.push(comment)
},
DELETE_COMMENT () {...}
};
let store = new Vuex.Store({
state: state,
mutations: mutations
});
export default store;
My Form.vue Component:
import { addComment } from '../../store/actions'
import { mapActions } from 'vuex'
export default {
vuex: {
actions: { addComment }
},
data () {
return {
content: '',
loading: false
}
},
props: {
id: Number,
model: String,
reply: {
type: Number,
default: 0
}
},
methods: {
sendComment: function () {
this.loading = true;
this.$store.dispatch('addComment', {
commentable_id: this.id,
commentable_type: this.model,
content: this.content,
reply: this.reply
}).catch((error) => {
this.error = error.response.data
}).then(() => {
this.loading = false
})
}
}
}

Categories