Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow for <component> to be a conditional wrapper ie. only render children. #12033

Closed
sugoidesune opened this issue Apr 26, 2021 · 12 comments
Closed

Comments

@sugoidesune
Copy link

What problem does this feature solve?

This use case is often called a conditional wrapper .
There sometimes is a need to wrap an element in a link or other elements for styling.
But instead of making multiple similar components or providing a placeholder tag like div .
It would be great to be able to simply leave out the wrapping. This is important since sometimes a even simple 'div' wrapper can break styling as well/ would need extra consideration/ work/ moving parts.

What does the proposed API look like?

I think the most clean and practical way for programmatic use would be to use the is prop.
Either by passing in a falsy value, or by passing in 'template', a tag that doesn't render in the html.

Template Output
<component :is="false"><span>Child</span></component> <span>Child</span>
OR
<component :is="'template'"><span>Child</span></component> <span>Child</span>
@posva
Copy link
Member

posva commented Apr 26, 2021

This can be achieved through a render function. It should be a functional component in Vue 2 but can be anything in Vue 3.

If you think something like this is worth pursuing in vue core rather than an external library, you can open a PR on the vuejs/rfcs repository

@posva posva closed this as completed Apr 26, 2021
@sugoidesune
Copy link
Author

sugoidesune commented Apr 26, 2021

I tried and so have others but so far nobody has cracked it. All solutions are workarounds with edge cases but none do it cleanly.

What i tried here: was:

export default {
functional: true,

props: ['tag'],

render(h, context) {
const { children, props, data } = context

//  The wrapper element itself becomes the html element/component.
if (props.tag) {
  return h(props.tag, data, children)
} else {
// And if the tag is false than it only renders children
  return children
}
},
}
<wrapper :tag=" shout ? 'h1' : false ">
Am I shouting?
</wrapper>

But I have difficulties making h(props.tag, data, children) as “transparent” as possible. And am getting errors when using components like <nuxt-link>

Any advice on what the best approach would be?

@Harrald
Copy link

Harrald commented Jun 3, 2022

I was suprised to find out this is not supported by Vue. We need this! 😄

@andrewsm80
Copy link

If something like this does eventually get implemented, I think it would make a lot more sense to use a directive, analogous to the v-if directive:

  <div v-wrap-if="someCondition">
    <span>
      This is a span that is sometimes wrapped up in a div.
    </span>
  </div>

@vincerubinetti
Copy link

vincerubinetti commented Jul 14, 2022

Why was this closed, this is a good feature to have. Edit: To be clear, I don't think this is the biggest deal in the world, but it does seem in line with the level of the other helpful conveniences Vue does.

My (annoying) solution is to have a special Wrapper.vue component:

<template>
  <component :is="tag" v-if="wrap">
    <slot />
  </component>
  <slot v-else />
</template>

<script setup lang="ts">
interface Props {
  tag: string;
  wrap: boolean;
}

defineProps<Props>();
</script>

Also, I'd lean towards OP's solution of using vue's existing fragment <template> tag, rather than implementing a new special v-wrap-if directive that people have to learn/remember.

<template>
  <component :is="shouldWrap ? 'div' : 'template'">
     some content
  </component>
</template>

(The above is a proposal, it doesn't currently work)

@robere2
Copy link

robere2 commented Aug 1, 2022

FYI, this has since been opened as an RFC: vuejs/rfcs#449

@distor-sil3nt
Copy link

Is there any news on this? The conditional wrapper feature would be really useful and I'm wondering whether someone already found a solution for this. Either as a custom directive or over the <compontent> tag.

@katerlouis
Copy link

Any status update? :)

@andrey-fanin
Copy link

We'll be glad to hear any news about solving this problem. :3

@katerlouis
Copy link

I've said it somewhere else, but why not here again:
I came up with a Vue 2 component to solve that use case

<script>
// inspired by a workaround for missing "optional wrapper" functionality in vue
// see: https://github.com/vuejs/rfcs/discussions/448#discussioncomment-5487435

export default {
  name: 'Fragment',

  render(h) {
    if (this.$slots && this.$slots.default) {
      return this.$slots.default;
    }

    return null;
  },
};
</script>

Usage:

<component
  :is="isLink ? 'a' : 'Fragment'"
  :href="someLinkComputed"
>
  I can even be plain text!
</component>

@janein
Copy link

janein commented Apr 2, 2024

Why was this closed, this is a good feature to have.

My (annoying) solution is to have a special Wrapper.vue component:

<template>
  <component :is="tag" v-if="wrap">
    <slot />
  </component>
  <slot v-else />
</template>

<script setup lang="ts">
interface Props {
  tag: string;
  wrap: boolean;
}

defineProps<Props>();
</script>

Also, I'd lean towards OP's solution of using vue's existing fragment <template> tag, rather than implementing a new special v-wrap-if directive that people have to learn/remember.

<template>
  <component :is="shouldWrap ? 'div' : 'template'">
     some content
  </component>
</template>

(The above is a proposal, it doesn't currently work)

You can even shorten your code to this:

<template>
    <component :is="tag" v-if="tag">
        <slot/>
    </component>
    <slot v-else/>
</template>

<script setup lang="ts">
defineProps<{
    tag?: string;
}>();
</script>

With this, you can use it very similar to your prefered proposal:

<wrapper :tag="shouldWrap ? 'div' : undefined">
  some content
</wrapper>

@mojabyte
Copy link

mojabyte commented May 1, 2024

A workaround is to use the Transition component:

<template>
    <component :is="condition ? Wrapper : Transition">
        <span>Child</span>
    </component>
</template>

<script setup>
import { Transition } from 'vue';
</script>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests