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.