Reputation: 2595
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.
Upvotes: 2
Views: 304
Reputation: 31352
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)
}
}
}
};
Upvotes: 4
Reputation: 82499
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>
Upvotes: 2