Home > front end >  Vue computed() not triggered on reactive map
Vue computed() not triggered on reactive map

Time:02-05

I have a reactive around a map that's initially empty: const map = reactive({});, and a computed that tells if the map has a key "key": const mapContainsKeyComputed = computed(() => map.hasOwnProperty("key")). The computed doesn't get updated when I change the map.

I stuck with this issue for a day and managed to come up with a minimum example that demonstrates the issue:

<script setup>
import {computed, reactive, ref, watch} from "vue";

const map = reactive({});
const key = "key";

const mapContainsKeyComputed = computed(() => map.hasOwnProperty(key))

const mapContainsKeyWatched = ref(map.hasOwnProperty(key));
watch(map, () => mapContainsKeyWatched.value = map.hasOwnProperty(key))
</script>

<template>
  Map: {{map}}
  <br/>
  Computed: does map contain "key"? {{mapContainsKeyComputed}}
  <br/>
  Watch: does map contain key? {{mapContainsKeyWatched}}
  <br/>
  <button @click="map[key] = 'value'">add key-value</button>
</template>

I've read a bunch of stackoverflow answers and the Vue docs, but I still can't figure it out.

  • why mapContainsKeyComputed doesn't get updated?
  • if the reactive doesn't "track" adding or removing keys to the map, why the Map: {{map}} (line 14) updates perfectly fine?
  • when I replace the map{} with an array[] and "hasOwnProperty" with "includes()", it works fine. How's that different?
  • how do I overcome this issue without the ugly "watch" solution where the "map.hasOwnProperty(key)" has to be duplicated?

EDIT: as mentioned by @estus-flask, this was a VueJS bug fixed in 3.2.46.

CodePudding user response:

Vue reactivity needs to explicitly support reactive object methods. hasOwnProperty is rather low-level so it hasn't been supported for some time. Without the support, map.hasOwnProperty(key) tries to access key on non-reactive raw object and doesn't trigger the reactivity, so the first computed call doesn't set a listener that could be triggered with the next map change.

One way this could be fixed is to either define key initially (as suggested in another answer), this is the legacy way to make reactivity work in both Vue 2 and 3:

const map = reactive({ key: undefined })

Another way is to access missing key property on reactive object:

const mapContainsKeyComputed = computed(() => map[key] !== undefined)

Yet another way is to use in operator. Since Vue 3 uses Proxy for reactivity, that a property is accessed can be detected by has trap:

const mapContainsKeyComputed = computed(() => key in map)

The support for hasOwnProperty has been recently added in 3.2.46, so the code from the question is supposed to be workable with the latest Vue version.

map is not really a map. This would be different in any Vue 3 version if Map were used, it's supported by Vue and it's expected that map.has(key) would trigger reactivity.

  • Related