I'm trying to use Immer with Vue. It appears that the state is updating, but Vue isn't updating the UI
// immutable.js
import produce, { applyPatches } from "immer"
let undo_buffer = []
export var state = { items: [] }
const handle_add_patch = (patch, inverse_patches) => {
console.log("Inverse Patches: ", inverse_patches)
undo_buffer.push(inverse_patches)
}
export const add_item = (item_name) => {
console.log("Starting add_item call")
const next_state = produce(
state,
draft => {
draft.items.push({ name: item_name })
},
handle_add_patch
)
console.log("next state: ", next_state)
state = next_state
}
export const undo = () => {
const undo_patch = undo_buffer.pop()
if (!undo_patch) return
let new_state = applyPatches(state, undo_patch)
console.log("New State: ", new_state)
state = new_state
}
<!-- item_list.Vue -->
<template>
<div>
<button @click.prevent="add_item()">Add Item</button>
{{ items }}
<button @click.prevent="undo()">Undo</button>
</div>
</template>
<script>
import * as immutable from './immutable.js'
export default {
computed: {
items: function(){ return immutable.state.items }
},
methods: {
add_item(){
console.log("State Before: ", immutable.state)
immutable.add_item("Hello")
console.log("State After: ", immutable.state)
},
undo(){
console.log("State Before: ", immutable.state)
immutable.undo()
console.log("State After: ", immutable.state)
}
}
}
</script>
The console.log shows that the items array is changing, but the items in the Vue template just shows an empty array. How can I make this visible within Vue?
Computed properties are cached and they aren't recomputed until associated component data is changed. Since Immer object isn't a part of the component, recomputation never occurs.
Caching can be prevented by using getter method instead of computed property:
{{ getItems() }}
...
methods: {
getItems: () => immutable.state.items
addItem() {
immutable.add_item("Hello");
this.$forceUpdate();
}
}
A more direct approach is to force a property to be recomputed:
data() {
return { _itemsDirtyFlag: 0 }
},
computed: {
items: {
get() {
this._itemsDirtyFlag; // associate it with this computed property
return immutable.state.items;
}
},
methods: {
updateItems() {
this._itemsDirtyFlag++;
},
addItem() {
immutable.add_item("Hello");
this.updateItems();
}
}
A similar approach that uses Vue.util.defineReactive internal is used by vue-recomputed.
This issue is not related with Immer. vuejs caches the computed properties hence they do not update when your data updates. This is mentioned clearly in vuejs docs at https://v2.vuejs.org/v2/guide/computed.html#Computed-Caching-vs-Methods
Instead of a computed property, we can define the same function as a method. For the end result, the two approaches are indeed exactly the same. However, the difference is that computed properties are cached based on their reactive dependencies. A computed property will only re-evaluate when some of its reactive dependencies have changed.
Since Immer is not a reactive dependency, the computed property never runs again.
Solution for this would be to change the computed property to a method. This way it will not be cached and the method will run each time with updated value.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With