egoist / vue-client-only

Vue component to wrap non SSR friendly components (428 bytes)
MIT License
474 stars 37 forks source link

Client-only content doesn't render in component's slot #105

Open DamianGlowala opened 3 years ago

DamianGlowala commented 3 years ago

Original issue reported at nuxt/nuxt.js#8579.

The content inside <client-only> tag doesn't render when the tag is placed in a component's slot of another component's slot, as shown in the following reproduction: codesandbox

@egoist any chance you could look into this issue please? I can see the repo hasn't been updated in a while but this missing functionality is crucial for some use-cases, when client-only tag is the only way to go in the nested slots structure :/

mrleblanc101 commented 3 years ago

I have this exact issue, I'm surprised nobody faced this issue before. v-calendar (v-date-picker) is not SSR compatible, so I wrap in client-only. This cause an SSR error:

vue.runtime.esm.js?2b0e:6427 Mismatching childNodes vs. VNodes: NodeList [comment], NodeList [VNode] I thought the problem was with v-calendar, but I still get the error if I only use client-only and standard HTML inside.

Here is my exact component.

<template>
    <v-widget :name="$t('agenda')">
        <div>
            <client-only>
                <div class="flex-container">
                    <v-date-picker v-model="selectedDate" :attributes="attributes" is-required />
                    <div class="events">
                        [...]
                    </div>
                </div>
            </client-only>
        </div>
    </v-widget>
</template>

And here is v-widget:

<template>
    <div class="widget">
        <h2 class="widget-name">{{ name }}</h2>
        <slot></slot>
    </div>
</template>
mrleblanc101 commented 3 years ago

From my understanding, Vue think it has to render a comment <!-- --> (the SSR placeholder), but on the client side, it realise it need to render a <div> which trigger the missmatch.

mrleblanc101 commented 3 years ago

Wrapping my slot in a <div> seems to fix the issue, but it's not actually possible in my case because of how my flex layout is made.

<template>
    <div class="widget">
        <h2 class="widget-name">{{ name }}</h2>
        <div>
            <slot></slot>
        </div>
    </div>
</template>
mrleblanc101 commented 3 years ago

My problem was with Nuxt component autoload/discovery. Replacing this with a plugin that manually import my global components fixed the problem.

components: [
    { path: '@/components/global', global: true },
],
akashrajum commented 3 years ago

Hey, I'm also stuck with a similar issue, as you can see in this code sandbox -- https://codesandbox.io/s/vue-horizontal-calendar-nuxt-demo-myytd?file=/pages/index.vue I am able to render component normally but if I pass it inside a template slot it's not rendering.

vue-horizontal-calendar is the component, I have declared twice but the one outside template is the only one rendering

akashrajum commented 3 years ago

One tiny workaround I found for now is by removing scoping to the template and using the slot where we have to pass the component as the default slot and template. So this basically - https://codesandbox.io/s/vue-horizontal-calendar-nuxt-demo-forked-hvx9e?file=/pages/index.vue

MarineLB commented 2 years ago

Had the same issue that I worked around by wrapping the entire component in the client only instead of the slot.

Initially, triggering the issue

<my-component>
   <client-only>{{ foobar }}</client-only>
</my-component>

The current fix, not ideal but works in our case

<client-only>
    <my-component>{{ foobar }}</my-component>
</client-only>
Razz21 commented 2 years ago

Not perfect, but I believe its possible to overcome this issue by rewriting <client-only> to a regular, stateful component. You lose of course some benefits or functional components (rendering multi root elements or performance), but at least renders slot content properly.

  render(h) {

    if (this._isMounted) {
      return this.$slots.default;
    }
    this.$once('hook:mounted', () => {
      this.$forceUpdate();
    });

    if (this.placeholderTag && (this.placeholder || this.$slots.placeholderSlot)) {
      return h(
        this.placeholderTag,
        {
          class: ['client-only-placeholder']
        },
        this.placeholder || this.$slots.placeholderSlot
      )
    }

    return this.$slots.default && this.$slots.default.length > 0 ? this.$slots.default.map(() => h(false)) : h(false)
  }
mrleblanc101 commented 1 year ago

Just faced this issue again in 2023. The problem is not with slot in general, but with named slots.

For exemple, this will work since it's a default slot (the <template> is not even necessary as eslint points-out)

Screenshot 2023-10-31 at 5 47 11 PM

But this won't work for some reason, still a default slot, but I added #default on the template.

Screenshot 2023-10-31 at 5 46 13 PM
mrleblanc101 commented 1 year ago

What seem to have worked for me was to use slot="slotName" instead of v-slot:slotName or the #slotName shorthand

raphaelbernhart commented 2 months ago

I can verify, this problem is still existing in 2024.