3

I created a composable with a reactive boolean. For some reason, the reactive boolean only works with .value in the template v-if. It doesn't automatically unwraps it inside the template.

But if I use a boolean ref directly on the vue component, it works. I reproduced it here

The code:

// useToggle.ts file
import { ref } from "vue";

const visible = ref(true);

export function useToggle() {
  function toggle() {
    visible.value = !visible.value;
  }

  function close() {
    visible.value = false;
  }

  return {
    visible,
    toggle,
    close,
  };
}


// App.vue file
<script setup lang="ts">
import { ref } from "vue";
import { useToggle } from "./useToggle";

const visible = ref(true);
const t = useToggle();
</script>

<template>
  <button @click="visible = !visible">toggle local</button>
  <div v-if="visible">local</div>

  <button @click="t.toggle">toggle global without value</button>
  <div v-if="t.visible">global (this doesn't work)</div>
  <div v-if="t.visible.value">global (this doesn't work)</div>

  {{t.visible}} (but this, updates)

</template>

I don't understand why the v-if doesn't work the same. What can I do to make the composable work like the local ref?

Artur Carvalho
  • 6,901
  • 10
  • 76
  • 105
  • 1
    Please, list useToggle for clarity, the question can become unintelligible if a demo goes offline. It's not a bad question, there's an inconsistency with interpolation syntax, it allows for additional unwrapping, the correct thing is to use {{t.visible.value}} universally, or stick to unwrapped refs by destructuring `t` – Estus Flask Jun 08 '23 at 10:10
  • Good point, thanks for the reminder. These reproduction sites are not very stable. I couldn't find mention of this inconsistency. I suppose there is some limitation because why wouldn't it unwrap on the v-if? If it's working like this it almost seems like a bug. In any case I think I'm going to start using value by default. – Artur Carvalho Jun 08 '23 at 12:30
  • 1
    Only top-level refs are expected to be unwrapped, as the answer states, it would be a bug if {{t.visible.value}} were not working, but it is, so I'd consider it an inconsistency. It makes sense to open an issue but this would be a breaking change for cases like this one – Estus Flask Jun 08 '23 at 12:58

2 Answers2

1

The unwrapping only applies if the ref is a top-level property on the template render context. And ref will also be unwrapped if it is the final evaluated value of a text interpolation.

https://vuejs.org/guide/essentials/reactivity-fundamentals.html#ref-unwrapping-in-templates

  • But how is it showing true when I put `{{t.visible}}`? Shouldn't it show object in that case? – Artur Carvalho Jun 08 '23 at 09:16
  • {{t.visible}} is the final evaluated value of a text interpolation in this case. But v-if="t.visible" is not. – Jun cong Tang Jun 09 '23 at 01:54
  • You can fix this by putting t.visible top-level property like this const { visible } = useToggle(); in script tag. – Jun cong Tang Jun 09 '23 at 02:34
  • It's still inconsistent. It also doesn't help that "It's easy to lose reactivity when destructuring reactive objects, while it can be cumbersome to use .value everywhere when using refs". So I avoid destructuring. Link: https://vuejs.org/guide/extras/reactivity-transform.html – Artur Carvalho Jun 09 '23 at 07:51
0
<script setup lang="ts">
import { ref } from "vue";
import { useToggle } from "./useToggle";

const visible = ref(true);
const {visible:v,toggle} = useToggle();
</script>

<template>
  <div>composable state: {{v }}</div>
  <div :style="{ display: 'flex' }">
    <div>
      <button @click="visible = !visible">toggle local</button>
      <div v-if="visible">local</div>
    </div>

    <div>
      <button @click="toggle">toggle global with value</button>
      <div v-if="v">toggle works here</div>
    </div>
    <div>
      <button @click="toggle">toggle global without value</button>
      <div v-if="v">toggle broken here</div>
    </div>
  </div>
</template>

VincentGuo
  • 247
  • 2
  • 9