quasarframework / quasar

Quasar Framework - Build high-performance VueJS user interfaces in record time
https://quasar.dev
MIT License
25.97k stars 3.52k forks source link

SSR - QExpansionItem hydration error - something with dynamic slots #14542

Open Evertvdw opened 2 years ago

Evertvdw commented 2 years ago

What happened?

We are building a cms system that will store information as JSON somewhere and render that at the server/client side. We also define slot content in there for quasar components and when using the QExpansionItem we get an hydration error when doing SSR.

It seems somehow the slot content is empty, with some effort I was able to make a minimal example where I replicate the JSON structure we use.

Inside the ExpansionItem.vue there are two slots (header and default) that can be filled with 1 or more components defined in the JSON structure. Inside the ItemContentBlock there is a v-for that loops through the slots and inside the slot another v-for that loops through the columns of that slot.

I also commented out a simple HTML collapse inside ExpansionItem using a details and summary element, that does not have the hydration errors. So it seems to be something specific to the ExpansionItem.

What did you expect to happen?

No hydration errors

Reproduction URL

https://stackblitz.com/edit/quasarframework-vxlqfr?file=src/components/ExpansionItem.vue

How to reproduce?

  1. Load the page, and open the developer console
  2. You should see 2 warnings and 1 error

Flavour

Quasar CLI with Vite (@quasar/cli | @quasar/app-vite)

Areas

Components (quasar), SSR Mode

Platforms/Browsers

Chrome

Quasar info output

No response

Relevant log output

No response

Additional context

No response

Evertvdw commented 1 year ago

I have done some more investigation into this and updated the reproduction. It seems to have to do specifically with render-function components which quasar uses.

The relevant code snippet:

  <component :is="getComponent(col.block)" :dynamicOptions="col.blockOptions">
    <!-- it seems that the 'slot' variable in this loop is not available in the component inside it. Also tried `#slot[slot.name]="slot"` but that does not render anything. It will prevent the error though ¯\_(ツ)_/¯  -->
    <template
      v-for="(slot, slotIndex) in col.slots"
      :key="slotIndex"
      #[slot.name]
    >
      <!-- Notice the slot?.columns, without the question mark it will produce an error and not render at all, as slot will be undefined -->
      <component
        :is="getComponent(column.block)"
        v-for="(column, columnIndex) in slot?.columns"
        :key="columnIndex"
        v-bind="column.blockOptions"
      />
    </template>
  </component>

In the second for loop the slot property defined in the for loop above is undefined when using render functions inside SSR. When the loaded component is a SFC there is no issue. I have very little knowledge about render functions so would appreciate some help with this. Maybe Quasar is missing something in the render function or there is some issue in Vue regarding this specific use of render functions.

Evertvdw commented 1 year ago

Maybe this is related? https://github.com/vuejs/vue/pull/12265

Evertvdw commented 1 year ago

I reduced the reproduction complexity and pinpointed the issue to this snippet:

  <q-expansion-item>
    <template v-for="(slot, slotIndex) in slots" :key="slotIndex" #[slot]>
      <div>
        {{ slot }}
      </div>
    </template>
  </q-expansion-item>
import { QExpansionItemSlots } from 'quasar';

const slots: (keyof QExpansionItemSlots)[] = ['header', 'default'];

Where slot will be undefined in SSR inside the template.

Evertvdw commented 1 year ago

Ok, did some more digging into this. I made a reproduction without using QExpansionItem but creating a custom render function component.

I also found that when placing the component (QExpansionItem or the Custom one) directly inside Index.vue, or some other root level component used inside router.js it will not give an Hydration error. However when you nest it 1 level deeper the exact same component will produce an hydration error.

See the components I updated here: https://stackblitz.com/edit/quasarframework-vxlqfr?file=src%2Fcomponents%2FExpansionItem.vue,src%2Fpages%2FIndexPage.vue

@rstoenescu Is this something you can work with? I want this fixed really badly, otherwise I will have to create my own custom QExpansionItem :(

Evertvdw commented 1 year ago

I was able to replicate the issue on the SFC playground, so I created an issue in the Vue repo also: https://github.com/vuejs/core/issues/7609. Hopefully it will be picked up there.