Vuex state not updating with mutation - javascript

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.

Related

Why the react component is not returning the current data from redux?

I have a react app that is connected with redux. The component has a form that makes a PUT call to the api when the form is submitted. When I submit the form, I can see that redux gets updated accordingly but when I try to access the redux state as a prop in my component, the props data does not return the current data and is off by 1. For example, here's the data in my redux store:
Redux store:
When I do console.log("THIS PROPS: ", this.props) in my component, I see that it accountError is showing up as null
When I dispatch the action again the second time, only then I see that I am getting the data from redux in my props:
Here is the code that I have currently:
OrgAccount.js
import { registerOrgAccount, getListOfOrgsAndAccts } from "../../store/actions";
handleSubmit = () => {
this.props.registerOrgAccount(this.state)
console.log("THIS PROPS: ", this.props)
if(this.props.accountError === null) {
this.toggleTab(this.state.activeTab + 1);
}
};
<Link
to="#"
className="btn w-lg"
onClick={() => {
if (this.state.activeTab === 1) {
this.handleSubmit();
}
}}
>
Next
</Link>
const mapStatetoProps = (state) => {
const { accounts, accountError, loading } = state.OrgAccount;
return { accounts, accountError, loading };
};
const mapDispatchToProps = (dispatch) => {
return {
getListOfOrgsAndAccts: () => {
dispatch(getListOfOrgsAndAccts())
},
registerOrgAccount: (data) => {
dispatch(registerOrgAccount(data))
},
}
}
export default connect(mapStatetoProps, mapDispatchToProps)(OrgAccount);
Reducer:
const initialState = {
accountError: null, accountsError: null, message: null, loading: null
}
const orgAccount = (state = initialState, action) => {
switch (action.type) {
case REGISTER_ORG_ACCOUNT:
state = {
...state,
account: null,
loading: true,
// accountError: null
}
break;
case REGISTER_ORG_ACCOUNT_SUCCESSFUL:
state = {
...state,
account: action.payload,
loading: false,
accountError: null
}
break;
case REGISTER_ORG_ACCOUNT_FAILED:
state = {
...state,
loading: false,
accountError: action.payload ? action.payload.response : null
}
break;
...
default:
state = { ...state };
break;
}
return state;
}
export default orgAccount;
Action
export const registerOrgAccount = (account) => {
return {
type: REGISTER_ORG_ACCOUNT,
payload: { account }
}
}
export const registerOrgAccountSuccessful = (account) => {
return {
type: REGISTER_ORG_ACCOUNT_SUCCESSFUL,
payload: account
}
}
export const registerOrgAccountFailed = (error) => {
return {
type: REGISTER_ORG_ACCOUNT_FAILED,
payload: error
}
}
Saga.js
import { registerOrgAccountSuccessful, registerOrgAccountFailed, getListOfOrgsAndAcctsSuccessful, getListOfOrgsAndAcctsFailed } from './actions';
import { putOrgAccount } from '../../../helpers/auth_helper';
function* registerOrgAccount({ payload: { account } }) {
try {
const response = yield call(putOrgAccount, {
orgId: account.orgId,
accountNumber: account.accountNumber,
accountName: account.accountName,
accountCode: account.accountCode,
urlLink: account.urlLink,
location: account.location,
accountType: account.accountType,
address: account.address,
city: account.city,
state: account.state,
zip: account.zip,
country: account.country,
email: account.email,
eula: "blah"
});
yield put(registerOrgAccountSuccessful(response));
} catch (error) {
yield put(registerOrgAccountFailed(error));
}
}
To understand the root cause here, I think it helps to know a little about immutability and how React rerenders. In short, React will rerender when it detects reference changes. This is why mutating a prop, wont trigger a rerender.
With that in mind, at the time you call handleSubmit, this.props.accountError is simply a reference to a value somewhere in memory. When you dispatch your action and your state is updated, a new reference will be created, which will trigger a rerender of your component. However the handleSubmit function that was passed to your element still references the old this.props.accountError, which is why it is still null.
You could get around this by implementing your check in the componentDidUpdate lifecycle method. E.g. something like this:
componentDidUpdate(prevProps) {
if (prevProps.accountError === null && this.props.accountError !== null) {
this.toggleTab(this.state.activeTab + 1)
}
}

Vuex not keeping currentUser state on page refresh

So I am Building a Vue.js SPA using Rails 6 api as the backend and Vue-cli (legacy webpack template)
When I sign a user in, everything works fine, I can see the users details, it sets my setCurrentUser mutation and state, as soon as I click away from my dashboard, I loose all my user's state. the vue dev tool panel essentially shows everything reset back to false.
I am rather new to Vue / Vuex so this may be an oversight on my part.
My signin method to grab the current user:
methods: {
signin () {
let formData = new FormData()
formData.append('user[email]', this.user.email)
formData.append('user[password]', this.user.password)
this.$http.plain.post('/signin', formData, { emulateJSON: true })
.then(response => this.signinSuccessful(response))
.catch(error => this.signinFailed(error))
},
signinSuccessful (response) {
if (!response.data.csrf) {
this.signinFailed(response)
return
}
this.$http.plain.get('/api/v1/me')
.then(meResponse => {
this.$store.commit('setCurrentUser', { currentUser: meResponse.data, csrf: response.data.csrf })
this.error = ''
this.$router.replace('/dashboard')
this.flashMessage.show({
status: 'info',
title: 'Signed In',
message: 'Signin successful, welcome back!'
})
})
.catch(error => this.signinFailed(error))
},
signinFailed (error) {
this.user.error = (error.response && error.response.data && error.response.data.error)
this.$store.commit('unsetCurrentUser')
},
checkSignedIn () {
if (this.$store.state.signedIn) {
this.$router.replace('/dashboard')
}
}
}
This image shows the api call to signin completes and returns the user object.
This image shows the Vue Panel sets the currentUser state and has the user object
Now when I go to do a page refresh or move away from my dashboard, I loose everything that was in state.
So like I said I'm brand new to Vuex, I have tried to use Vue.set and $set on my mutations in store.js but this did not remedy the problem either..?
Here is my store.js file:
import Vue from 'vue'
import Vuex from 'vuex'
import createPersistedState from 'vuex-persistedstate'
Vue.use(Vuex)
export const store = new Vuex.Store({
state: {
currentUser: {},
signedIn: false,
csrf: null
},
mutations: {
setCurrentUser (state, { currentUser, csrf }) {
state.currentUser = currentUser
state.signedIn = true
state.csrf = csrf
},
unsetCurrentUser (state) {
state.currentUser = {}
state.signedIn = false
state.csrf = null
},
refresh (state, csrf) {
state.signedIn = true
state.csrf = csrf
}
},
getters: {
isOwner (state) {
return state.currentUser && state.currentUser.role === 'owner'
},
isManager (state) {
return state.currentUser && state.currentUser.role === 'manager'
},
isAdmin (state) {
return state.currentUser && state.currentUser.role === 'admin'
},
isUser (state) {
return state.currentUser && state.currentUser.role === 'user'
},
isSignedIn (state) {
return state.signedIn === true
}
},
plugins: [
createPersistedState({
})
]
})
Any assistance here would be greatly appreciated!
Add plugins: [createPersistedState()], when creating store instance
export default new Vuex.Store({
plugins: [createPersistedState()],}

How to update Vuex store correctly?

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).

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