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
.