Is there a way to use promise in action with this structure Vuex - vue.js

I have an action which fires on form submit in component
this.$store.dispatch('registerAction', this.registerData);
Also in this component I want to show message like "Registration is successful".
For that I want to listen for property in my this.$store.state registerSuccess which is false by default.
registerAction (Vuex)
return this.api.registerUser()
.then(response => {
if(response.success) {
return store.dispatch('registerSuccess');
}
return store.dispatch('registerFail');
});
registerSuccess mutation sets state.registerSuccess = true; (and fail sets it to false)
Question:
How can I listen for changes to state.registerSuccess in this case? If I'm putting it in computed it returns false. Although it does change in Vuex store to true
computed: {
registerSuccess() {
console.log(this.$store.state.registerSuccess)
return this.$store.state.registerSuccess;
}
},

You've mentioned that registerSuccess is a mutation which also means that you have put it inside the mutation property inside your store object like this:
{
state: {
registerSuccess: false
},
mutations: {
registerSuccess(state) {
state.registerSuccess = true;
}
}
}
In that case, you would need to use commit() to execute the registerSuccess mutation.
Your code should be:
return this.api.registerUser()
.then(response => {
if(response.success) {
return store.commit('registerSuccess');
}
return store.commit('registerFail');
});

Related

vue computed not firing when vuex state changes

I dont know why, but my computed property is not firing when state changes.
I am dispatching actions and as a result state changes. but data is not firing at all. wanna help :(
//store
mutations: {
setStudyData(state, payload) {
state.studyData = [...payload];
},
},
actions: {
async postLogin({ state, commit, dispatch }, { userId, userPass }) {
try {
const res = await axios.post(`${url}/v1/api/user/auth/signin`, {
userId,
userPass,
});
await commit("setSeq", res.data.user_seq);
await commit("setToken", res.data.accessToken);
await dispatch("getStudyLi");
console.log(state.studyData); //i can see the state changes here
} catch {
console.log("error");
}
},
computed: {
data(){
console.log(this.datas) //not working
return this.$store.state.login.studyData
}
},
You don't have any this.datas property in the code shown.
It is also important how do use the computed. It runs the computed method only if there is some call of computed value. You can not expect method to be called if you do not use this.data property in your component.
U can just use mapState instead, if u not gonna process your studyData anymore and save your time writing this.$store.state.login.studyData
computed: {
...mapState([
"studyData",
"studyData2",
]),
}

Returning a getters in a computed create a loop

I am calling inside the computed an action from the store to run it and after I am returning a getter, this will create a loop.
The HTML
{{loadedProjects}}
The computed
computed: {
loadedProjects() {
this.$store.dispatch("getProjects");
return this.$store.getters.loadedProjects;
}
}
The store
import Vuex from "vuex";
import axios from "axios";
const createStore = () => {
return new Vuex.Store({
state: {
loadedProjects: []
},
mutations: {
setProjects(state, projects) {
state.loadedProjects = projects
}
},
actions: {
getProjects(vuexContext) {
console.log("hello1")
return axios.get("THE API URL")
.then(res => {
console.log("hello2")
vuexContext.commit("setProjects", res.data);
})
.catch(e => console.log(e));
}
},
getters: {
loadedProjects(state) {
return state.loadedProjects;
}
}
});
};
export default createStore;
I expect to call my action to populate my state and after to return my state to render my data.
What is the point of using the store action that makes an API call inside the computed property ... maybe you want to trigger loadedProjects change ? ....computed property is not asynchronous so either way the return line will be executed before the you get the response... you might try vue-async-computed plugin OR just use the call on the created hook like you have done which is the better way and you don't have to use a computed property you can just {{ $store.getters.loadedProjects }} on your template
Computed properties should not have side effects (e.g. calling a store action, changing data, and so on). Otherwise it can happen that the triggered side effect could lead to a re-rendering of the component and possible re-fetching of the computed property. Thus, an infinite loop
I changed the code like that:
created: function () {
this.$store.dispatch("getProjects")
},
computed: {
loadedProjects() {
return this.$store.getters.loadedProjects
}
}
It is working now but I would like to know but I have that problem working inside the computed and also I wonder if it's the best solution. Any help????

VueJS data doesnt change on URL change

My problem is that when I go from one user page to another user page the info in component still remains from first user. So if I go from /user/username1 to /user/username2 info remains from username1. How can I fix this ? This is my code:
UserProfile.vue
mounted() {
this.$store.dispatch('getUserProfile').then(data => {
if(data.success = true) {
this.username = data.user.username;
this.positive = data.user.positiverep;
this.negative = data.user.negativerep;
this.createdAt = data.user.createdAt;
this.lastLogin = data.user.lastLogin;
data.invites.forEach(element => {
this.invites.push(element);
});
}
});
},
And this is from actions.js file to get user:
const getUserProfile = async ({
commit
}) => {
try {
const response = await API.get('/user/' + router.currentRoute.params.username);
if (response.status === 200 && response.data.user) {
const data = {
success: true,
user: response.data.user,
invites: response.data.invites
}
return data;
} else {
return console.log('Something went wrong.');
}
} catch (error) {
console.log(error);
}
};
Should I add watch maybe instead of mounted to keep track of username change in url ?
You can use watch with the immediate property, you can then remove the code in mounted as the watch handler will be called instead.
watch: {
'$route.params.username': {
handler: function() {
this.$store.dispatch('getUserProfile').then(data => {
if(data.success = true) {
this.username = data.user.username;
this.positive = data.user.positiverep;
this.negative = data.user.negativerep;
this.createdAt = data.user.createdAt;
this.lastLogin = data.user.lastLogin;
data.invites.forEach(element => {
this.invites.push(element);
});
}
});
},
deep: true,
immediate: true,
},
}
Your page is loaded before the data is retrieved it seems, you need put a "loading" property in the data and have a v-if="!loading" for your component then it will only render once the display is updated. Personally I would avoid watch if I can it is not great for performance of for fine grained handling.
Yes you should add wach on statement that contain user info.(you may have a problem to watch on object, so you can save user info in json, but im not sure). When user changing - call action, after recived response call mutation that should change a state, then watch this state.
And you might use better syntax to receive data from store. That is really bad idea call dispatch directly from your mouted hook, use vuex documentation to make your code better.

Call a function after state changes

I'm building a React Native app and when one button is pressed I want to call two functions. The first one will make a get call and set the state loading: true, the second one will show a popup with the result of that get call.
I am calling the second function only if loading === false but it is executed immediately after the first one before the state can change, because loading is false by default. I can resolve this with setTimeout but I was wondering if there was a cleaner way to do this.
onPress() {
this.props.getUsers();
setTimeout(() => {
if (this.props.loading === false) {
this.props.popUpVisible();
}
}, 1000);
}
You can create callback function for that
getUsers = (callback) => {
//do whatever you want
//when it's done
callback();
}
In onPress function
onPress = () => {
this.props.getUsers(() => {
if (this.props.loading === false) {
this.props.popUpVisible();
}
});
}
setState Function can take two param:
setState(updater, callback)
setState({loading:true},() => {
//this fires once state.loading === true
})
Use getDerivedStateFromProps. It always fire when component's props change.
Below is the example.
class EmailInput extends Component {
state = {
email: this.props.defaultEmail,
prevPropsUserID: this.props.userID
};
static getDerivedStateFromProps(props, state) {
// Any time the current user changes,
// Reset any parts of state that are tied to that user.
// In this simple example, that's just the email.
if (props.userID !== state.prevPropsUserID) {
return {
prevPropsUserID: props.userID,
email: props.defaultEmail
};
}
return null;
}
// ...
}

"Simulate" mutations in vuex

import { remoteSettings } from 'somewhere';
const store = {
state: {
view: {
foo: true
}
},
mutations: {
toggleFoo(state) {
state.view.foo = !state.view.foo;
}
},
actions: {
async toggleFoo({ state, commit }) {
commit('toggleFoo');
await remoteSettings.save(state);
}
}
};
Say I have a simple store like this. toggleFoo action applies the mutation, then saves the new state by making an async call. However, if remoteSettings.save() call fails, local setting I have in the store and remote settings are out of sync. What I really want to achieve in this action is something like this:
async toggleFoo({ state, commit }) {
const newState = simulateCommit('toggleFoo');
await remoteSettings.save(newState);
commit('toggleFoo');
}
I'd like to get the new state without actually committing it. If remote call succeeds, then I'll actually update the store. If not, it's going to stay as it is.
What's the best way to achieve this (without actually duplicating the logic in the mutation function)? Maybe "undo"? I'm not sure.
One way of doing this would be: (credit to #Bert for correcting mistakes)
Store the old state using const oldState = state; before committing the mutation.
Wrap the async call in a try-catch block.
If the remoteSettings fails it will pass the execution to catch block.
In the catch block commit a mutation to reset the state.
Example:
const store = {
state: {
view: {
foo: true
}
},
mutations: {
toggleFoo(state) {
state.view.foo = !state.view.foo;
},
resetState(state, oldState){
//state = oldState; do not do this
//use store's instance method replaceState method to replace rootState
//see : https://vuex.vuejs.org/en/api.html
this.replaceState(oldState)
}
},
actions: {
async toggleFoo({ state, commit }) {
const oldState = JSON.parse(JSON.stringify(state)); //making a deep copy of the state object
commit('toggleFoo');
try {
await remoteSettings.save(newState);
//commit('toggleFoo'); no need to call this since mutation already commited
} catch(err) {
//remoteSettings failed
commit('resetState', oldState)
}
}
}
};
Borrowing code from #VamsiKrishna (thank you), I suggest an alternative. In my opinion, you want to send the changes to the server, and update the local state on success. Here is a working example.
To prevent duplicating logic, abstract the change into a function.
console.clear()
const remoteSettings = {
save(state){
return new Promise((resolve, reject) => setTimeout(() => reject("Server rejected the update!"), 1000))
}
}
function updateFoo(state){
state.view.foo = !state.view.foo
}
const store = new Vuex.Store({
state: {
view: {
foo: true
}
},
mutations: {
toggleFoo(state) {
updateFoo(state)
},
},
actions: {
async toggleFoo({ state, commit }) {
// Make a copy of the state. This simply uses JSON stringify/parse
// but any technique/library for deep copy will do. Honestly, I don't
// think you would be sending the *entire* state, but rather only
// what you want to change
const oldState = JSON.parse(JSON.stringify(state))
// update the copy
updateFoo(oldState)
try {
// Attempt to save
await remoteSettings.save(oldState);
// Only commit locally if the server OKs the change
commit('toggleFoo');
} catch(err) {
// Otherwise, notify the user the change wasn't allowed
console.log("Notify the user in some way that the update failed", err)
}
}
}
})
new Vue({
el: "#app",
store,
computed:{
foo(){
return this.$store.state.view.foo
}
},
mounted(){
setTimeout(() => this.$store.dispatch("toggleFoo"), 1000)
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.0.1/vuex.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.9/vue.js"></script>
<div id="app">
<h4>This value never changes, because the server rejects the change</h4>
{{foo}}
</div>