Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Immer data not updating in Vue

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?

like image 598
Dave Avatar asked Jan 17 '26 23:01

Dave


2 Answers

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.

like image 163
Estus Flask Avatar answered Jan 20 '26 12:01

Estus Flask


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.

like image 36
Vijay Joshi Avatar answered Jan 20 '26 11:01

Vijay Joshi