Home > front end >  How to make single property in array reactive when using `ref` instead of `reactive`?
How to make single property in array reactive when using `ref` instead of `reactive`?

Time:02-06

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: working version

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:

not working version

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.

  • Related