On vue 3 v-if not working for ref value returned from composable
Asked Answered
K

4

6

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?

Korean answered 8/6, 2023 at 8:59 Comment(3)
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 tWade
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.Korean
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 oneWade
P
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

Phoney answered 8/6, 2023 at 9:12 Comment(4)
But how is it showing true when I put {{t.visible}}? Shouldn't it show object in that case?Korean
{{t.visible}} is the final evaluated value of a text interpolation in this case. But v-if="t.visible" is not.Phoney
You can fix this by putting t.visible top-level property like this const { visible } = useToggle(); in script tag.Phoney
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: vuejs.org/guide/extras/reactivity-transform.htmlKorean
I
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>

Ileenileitis answered 27/6, 2023 at 10:14 Comment(0)
C
0
<template>
    <AppHeader />
    <div class="view z-page">
        <!-- <BreadCrumbs /> -->
        <router-view v-show="isReady"></router-view>
        <AppLoading v-show="!isReady"/>
    </div>
    <AppFooter />
</template>

Yeah when i render v-show on router view and uncomment the commented it gives me back the same error

Cinerator answered 31/12, 2023 at 4:32 Comment(0)
C
-1
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router';
import AppHeader from '@/components/header/AppHeader.vue'
import AppFooter from '@/components/AppFooter.vue'
import AppLoading from '@/components/utils/AppLoading.vue';
// import BreadCrumbs from './components/utils/BreadCrumbs.vue'
// const isReady = ref(false)
// const router = useRouter()
// router.beforeEach((to, from, next) => {
//  isReady.value = false;
//  setTimeout(()=> isReady.value = true)
//  next()
// })
    
</script>
    
<template>
  <AppHeader />
  <div class="view z-page">
    <!-- <BreadCrumbs /> -->
    <router-view ></router-view>
    <AppLoading v-show="false"/>
  </div>
  <AppFooter />
</template>

after adding a ref to the app.vue, the same thing happened to me, and when I remove it it goes back to its normal state.

Cinerator answered 31/12, 2023 at 3:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.