I have a component that displays rows of data which I want to toggle to show or hide details. This is how this should look:
This is done by making the mapping the data to a new array and adding a opened
property. Full working code:
<script setup>
import { defineProps, reactive } from 'vue';
const props = defineProps({
data: {
type: Array,
required: true,
},
dataKey: {
type: String,
required: true,
},
});
const rows = reactive(props.data.map(value => {
return {
value,
opened: false,
};
}));
function toggleDetails(row) {
row.opened = !row.opened;
}
</script>
<template>
<div>
<template v-for="row in rows" :key="row.value[dataKey]">
<div>
<!-- Toggle Details -->
<a @click.prevent="() => toggleDetails(row)">
{{ row.value.key }}: {{ row.opened ? 'Hide' : 'Show' }} details
</a>
<!-- Details -->
<div v-if="row.opened" style="border: 1px solid #ccc">
<div>opened: <pre>{{ row.opened }}</pre></div>
<div>value: </div>
<pre>{{ row.value }}</pre>
</div>
</div>
</template>
</div>
</template>
However, I do not want to make the Array deeply reactive, so i tried working with ref
to only make opened
reactive:
const rows = props.data.map(value => {
return {
value,
opened: ref(false),
};
});
function toggleDetails(row) {
row.opened.value = !row.opened.value;
}
The property opened
is now fully reactive, but the toggle doesn't work anymore:
How can I make this toggle work without making the entire value reactive?
CodePudding user response:
The problem seems to come from Vue replacing the ref with its value.
When row.opened
is a ref initialized as ref(false)
, a template expression like this:
{{ row.opened ? 'Hide' : 'Show' }}
seems to be interpreted as (literally)
{{ false ? 'Hide' : 'Show' }}
and not as expected as (figuratively):
{{ row.opened.value ? 'Hide' : 'Show' }}
But if I write it as above (with the .value
), it works.
Same with the if
, it works if I do:
<div v-if="row.opened.value">
It is interesting that the behavior occurs in v-if
and ternaries, but not on direct access, i.e. {{ rows[0].opened }}
is reactive but {{ rows[0].opened ? "true" : "false" }}
is not. This seems to be an issue with Vue's expression parser. There is a similar problem here.