Vue’s Composition API wraps reactive state in a Ref object through functions such as ref(), computed(), toRef(), customRef(), shallowRef(), toRefs(), or defineModel(). A Ref is a plain object with a single value property; reading or writing that property is what triggers Vue’s reactivity.

Why is this an issue?

A Ref object can be used directly as an operand: in a comparison, an arithmetic or increment expression, a template literal, a condition, or an assignment. Doing so operates on the Ref object itself instead of the value it wraps. Because a Ref object is always truthy, conditions such as if (count) are always true regardless of the underlying value. Arithmetic and string operations produce results based on the object rather than the wrapped number or string, and assigning a new value to the identifier replaces the reactive binding instead of updating the state that Vue tracks.

This pattern has no legitimate use: fix it by accessing or updating the wrapped value through .value.

Code examples

Noncompliant code example

<script setup>
import { ref } from 'vue';

const count = ref(0);

function increment() {
  count++; // Noncompliant: "count" is a ref; use "count.value++" instead
}
</script>

Compliant solution

<script setup>
import { ref } from 'vue';

const count = ref(0);

function increment() {
  count.value++;
}
</script>

Noncompliant code example

<script setup>
import { computed, ref } from 'vue';

const items = ref([]);
const hasItems = computed(() => items.value.length > 0);

function checkout() {
  if (hasItems) { // Noncompliant: "hasItems" is a ref; use "hasItems.value" instead
    items.value = [];
  }
}
</script>

Compliant solution

<script setup>
import { computed, ref } from 'vue';

const items = ref([]);
const hasItems = computed(() => items.value.length > 0);

function checkout() {
  if (hasItems.value) {
    items.value = [];
  }
}
</script>

Resources

Documentation