skip to Main Content

In Vue 3, how can an element be conditionally wrapped rather than creating two seperate blocks with v-if directive?

I have a simplified example here, but the question deals with conditional wrapping. There are a few issues here, in the html block there is a lot of coupling with state in the script setup block, so abstracting that to a child component is more trouble than it’s worth. There would be mixed state and it would be a mess having to deal with emitting data from child to parent. Second, because of the size of the html block, creating manual render functions would also be a mess.

The below code does work, but there is a big block that is duplicated just because I need an extra wrapper. Note that this question is not about how css could be used to fix this.

<script setup>
const isMobile = ref(true)
</script>

<template>
  <div v-if="isMobile" class="mobile-wrapper">
    <div class="product-meta">
      <!-- long html block tightly coupled with script setup block -->
    </div>
  </div>
  <div v-else class="product-meta">
    <!-- same html block -->
  </div>
</template>

2

Answers


  1. We can achieve conditional wrapping of an element without resorting to duplicate HTML blocks using a combination of computed properties and template slots

    <script setup>
    const isMobile = ref(true);
    
    const wrapperElement = computed(() =>
      isMobile.value ? 'div class="mobile-wrapper"' : 'div'
    );
    </script>
    

    Use the v-slot directive on the wrapper element to define a slot where the wrapped content will be rendered

    Code snippet

    <template :tag="wrapperElement">
      <div class="product-meta">
        </div>
    </template>
    
    Login or Signup to reply.
  2. I’m usually do this that way through a dynamic functional component, since I love to use render functions:

    VUE SFC PLAYGROUND

    <script setup>
    import { ref } from 'vue'
    import Wrap from './Wrap.vue'
    
    const isWrapped = ref(false);
    
    </script>
    
    <template>
      <wrap :wrapper="isWrapped ? ['div', {class: 'wrapper'}] : null">
        <div>I'AM WRAPPED</div>
      </wrap>
      <button @click="isWrapped = !isWrapped">Toggle wrapping</button>
    </template>
    

    Wrap.vue

    <script setup>
    import {h} from 'vue';
    
    const props = defineProps({
      wrapper: Array
    });
    
    </script>
    
    <template>
      <component :is="wrapper ? h(...wrapper, $slots.default()) : $slots.default"/>
    </template>
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search