gs-shop / vue-slick-carousel

🚥Vue Slick Carousel with True SSR Written for ⚡Faster Luxstay
https://gs-shop.github.io/vue-slick-carousel/
Other
810 stars 184 forks source link

Mismatching childNodes vs. VNodes with responsive options (nuxtjs) #94

Open Brewal opened 4 years ago

Brewal commented 4 years ago

When using the responsive option and matching a breakpoint, after a page reload, an error is thrown :

[Vue warn]: The client-side rendered virtual DOM tree is not matching server-rendered content. This is likely caused by incorrect HTML markup, for example nesting block-level elements inside <p>, or missing <tbody>. Bailing hydration and performing full client-side render.

I'm using this configuration :

{
  speed: 500,
  arrows: false,
  dots: true,
  slidesToShow: 3,
  slidesToScroll: 3,
  responsive: [
    {
      breakpoint: 576,
      settings: {
        slidesToShow: 1,
        slidesToScroll: 1
      }
    }
  ]
}
PHPDevFrozenRain commented 4 years ago

yes. I get this promlem too. Im waiting solution problem

kyuwoo-choi commented 4 years ago

I've tested using https://github.com/kyuwoo-choi/nuxt-vue-slick-carousel-example with responsive settings. And I can't reproduce this issue. @Brewal @PHPDevFrozenRain could you provide a demo I can reproduce?

Besides of that, I see nuxt server renders not considering responsive setting as nuxt can not know the client screen size. It's a limitation of this carousel.

PHPDevFrozenRain commented 4 years ago

the bug happens 2 div and more. Here is an example (uncomment responsive) `

1
                            <div>2</div>
                        </vue-slick-carousel>`
            settings:{
                slidesToShow: 3,
                arrows: true,
//                responsive: [
//                    {
//                        breakpoint: 2500,
//                        settings: {
//                            slidesToShow: 3,
//                            slidesToScroll: 1,
//                            infinite: true,
//                            dots: true,
//                            arrows: true,
//                        }
//                    },
//                    {
//                        breakpoint: 991,
//                        settings: {
//                            slidesToShow: 2,
//                            slidesToScroll: 1,
//                            dots: true,
//                            arrows: true,
//                        }
//                    },
//                    {
//                        breakpoint: 767,
//                        settings: {
//                            slidesToShow: 1,
//                            slidesToScroll: 1,
//                            dots: true
//                        }
//                    },
//                ]
            },
PHPDevFrozenRain commented 4 years ago

Besides of that, I see nuxt server renders not considering responsive setting as nuxt can not know the client screen size. It's a limitation of this carousel.

yes. but it continious reproducing the bug and doesn't let use ssr. I noticed it on version 1.0.5 but I'm not sure about 1.0.3 version. In your example you use 1.0.2 version.

julpat commented 4 years ago

I have this issue with 1.0.2 (and 1.0.5 also). To reproduce, you need to have "universal" mode in Nuxt.js and refresh the page on some "breakpoint" from responsive setting. The error (warning) will show in console.

subjacked commented 4 years ago

I temporarily solved this by wrapping the carousel with responsive configuration in <client-only>...</client-only>

cherneckiy commented 4 years ago

Same problem. Tell me how to fix it?

kyuwoo-choi commented 4 years ago

I still have no idea how to support responsive SSR. Yet, I reproduced this error and need to think of ways how to deal with it. I'd love to listen to the opinions.

julpat commented 4 years ago

Sadly I dont have so much time to consider all consequences of your code, but from quick check, I would start to avoid using created method for DOM manipulating. In InnerSlider.vue::72 you got ssrInit() which is making preClones and postClones. It is not importent to have clones in SSR version, also you dont know the window sizes, so can not decide corectly based on "responsive". Also in VueSlickCarousel.vue::105 is weird to call this.makeBreakpoints() in created methods. I guess it should be in mounted hook?

Brewal commented 4 years ago

I understand the responsiveness of the carousel can cause some issue when used in conjunction of SSR. As @julpat said, you should have some options to prevent this issue (you should indeed be using the mounted event to render and patch the virtual DOM) but maybe it will take more efforts to make it work.

I didn't have the time either to look at the source code but I hope I will.

For now, maybe I will conditionally render multiple carousel based on the user agent using a library such as mobile-device-detect or just ignore the error !

PHPDevFrozenRain commented 4 years ago

are there going to be fixes? many people use carousel because of ssr.

julpat commented 4 years ago

Agree, maybe the Hotfix is removing "supports true SSR" from README.md 🤔

kyuwoo-choi commented 4 years ago

sure It should be fixed. I just have to find my time on it.

escael commented 4 years ago

Same problem!!!

jaytrepka commented 4 years ago

same here, will it be fixed?

dweiss-et commented 4 years ago

I've found a workaround which works for SSR and does not add an additional performance burden for mobile devices.

It defines an initial mobile friendly this.settings.slidesToShow of 2 and an empty this.settings.responsive: [] . The required/desired responsive settings are applied once on VueSlickCarousel init.

Note: this.areResonsiveSettingsApplied === false check is used in order to prevent that this.responsiveSettings are applied multiple times through an init loop.

<template>
  <div class="h-screen">
    <VueSlickCarousel v-bind="settings" @init="initHandler">
      <div
        v-for="(value, index) in items"
        :key="index"
        class="h-40"
        :class="[`bg-pink-${value}`]"
      >
        {{ value }}
      </div>
    </VueSlickCarousel>
  </div>
</template>

<script>
import VueSlickCarousel from 'vue-slick-carousel'
import 'vue-slick-carousel/dist/vue-slick-carousel.css'
// optional style for arrows & dots
import 'vue-slick-carousel/dist/vue-slick-carousel-theme.css'

export default {
  name: 'MyComponent',
  components: { VueSlickCarousel },
  data() {
    return {
      items: [100, 200, 300, 400, 500, 600, 700, 800, 900],
      settings: {
        dots: false,
        infinite: true,
        autoplay: true,
        autoplaySpeed: 3000,
        speed: 1000,
        slidesToShow: 2,
        slidesToScroll: 1,
        initialSlide: 0,
        cssEase: 'ease-in-out',
        responsive: [],
      },
      responsiveSettings: [
        {
          breakpoint: 1201,
          settings: {
            slidesToShow: 4,
          },
        },
        {
          breakpoint: 993,
          settings: {
            slidesToShow: 3,
          },
        },
        {
          breakpoint: 769,
          settings: {
            slidesToShow: 2,
          },
        },
      ],
      areResonsiveSettingsApplied: false,
    }
  },
  methods: {
    initHandler() {
      console.log('> Handler : init')
      if (this.areResonsiveSettingsApplied === false) {
        this.applyResponsiveSettings()
      }
    },
    applyResponsiveSettings() {
      console.log('> Method : applyResponsiveSettings')
      this.settings.responsive = this.responsiveSettings
      this.settings.slidesToShow = 5
      this.areResonsiveSettingsApplied = true
    },
  },
}
</script>
<style>
.slick-slider {
  & .slick-arrow.slick-arrow {
    @apply bg-red-600;
  }
}
</style>
rizkysyazuli commented 3 years ago

the solution from @dweiss-et works for me. although i'm curious about the missing @reInit event handler in your code example above.

dweiss-et commented 3 years ago

@rizkysyazuli you are right there is an @reInit="reinitHandler unused event handler declaration in my example. I have removed it from the example because it's not required.

gullocean commented 3 years ago

This worked for me.

<template>
  <vue-slick-carousel v-if="showSlider" v-bind="sliderSettings">
    ...
  </vue-slick-carousel>
</template>

<script>
export default {
  data () {
    return {
      showSlider: true,
      sliderSettings: {
        ... // do not include the responsive settings here
      }
    }
  },

  mounted () {
    this.showSlider = false

    this.$nextTick(() => {
      this.sliderSettings.responsive = [{
        breakpoint: 1024,
        settings: {
          ...
        }
      }]

      this.showSlider = true
    })
  }
}
</script>
milindsingh commented 3 years ago

@gullocean Thanks Its working but it flickers as on initial page load, 2 are loaded and then a single is switched in mobile.

Anyway to fix this?

irving-caamal commented 3 years ago

If u are using Nuxt.js you can use device module to handle this error and don't loss SSR features on this plugin

slidesToShow: this.$device.isMobile ? 1 : 3, slidesToScroll: this.$device.isMobile ? 1 : 3,

This is not elegant at all but works as expected.

JeffJassky commented 2 years ago

Using the solution provided by @dweiss-e AND ensuring that the # of items in the carousel stayed consistent across both the server and the client solved this issue for me.

Originally, due to a particular cache strategy I was using, the number of items in the carousel could change depending on whether it was rendered on the client or the server.

lkjimy commented 2 years ago

Having the same issue on 1.0.6. Using <client-only /> for now.

vedmant commented 1 year ago

I did following instead:

  computed: {
    carouselOpts () {
      switch (this.screenSize) {
        case 'sm': return { centerMode: false, slidesToShow: 1, slidesToScroll: 1 }
        case 'md': return { centerMode: false, slidesToShow: 2, slidesToScroll: 2 }
        default: return { centerMode: true, slidesToShow: 3, slidesToScroll: 3 }
      }
    },
  },
    <vue-slick-carousel
      :arrows="true"
      v-bind="carouselOpts"
      swipe-to-slide
    >

this.screenSize is set first on the backend based on 'mobile-detect' package.