akiran / react-slick

React carousel component
http://react-slick.neostack.com/
MIT License
11.72k stars 2.1k forks source link

SSR and responsive mode #1245

Open padupuy opened 6 years ago

padupuy commented 6 years ago

I already open this issue on the nextjs repo but it seems to be a problem with react-slick.

Describe the bug When I use next.js with react-slick and responsive options, there is a bug during the rehydration phase, the src attribute of the images are mixed between the slides.

The error message is Warning: Prop src did not match. Server: "http://via.placeholder.com/350x150?text=7" Client: "http://via.placeholder.com/350x150?text=8"

To Reproduce I made an example from examples folder : https://github.com/padupuy/next.js/tree/feature/with-react-slick

The code is right here : https://github.com/padupuy/next.js/blob/feature/with-react-slick/examples/with-react-slick/pages/index.js

Expected behavior The image sources must not be mixed

Screenshots

capture d ecran 2018-05-16 a 18 29 07

capture d ecran 2018-05-16 a 18 31 01

System information

virajsinha commented 6 years ago

I am facing a similar issue with SSR. Were you able to find a fix?

masterGennadij commented 6 years ago

Is any solution to prevent this bug? Thank you.

davidsanchez96 commented 6 years ago

I don't know if is the best approach but I solved this problem by using dynamic import

import dynamic from 'next/dynamic'

const SliderComponentWithNoSSR = dynamic(import('../components/SliderComponent'), {
  ssr: false
})

export default () =>
  <div>
    <SliderComponentWithNoSSR />
  </div>
tedlin182 commented 6 years ago

I too am having this same issue. For now I have to use another plugin since it's mixing up the content for me

AndriiUhryn commented 5 years ago

+1

felipetoffolo1 commented 5 years ago

+1

josheche commented 5 years ago

+1

camwes commented 5 years ago

+1

xmanzero commented 5 years ago

Thank you @davidsanchez96

lucanovera commented 5 years ago

+1

goshdarnheck commented 5 years ago

+1

chaance commented 5 years ago

Following @davidsanchez96's lead, I still wanted the actual content to be rendered on both the server and the client, so this was my approach.

import React, { Component } from 'react';
import dynamic from 'next/dynamic';
import classNames from 'classnames';

class Slider extends Component {
  state = {
    isServer: true
  };

  componentDidMount() {
    this.setState((state) => state.isServer && { isServer: false });
  }

  render() {
    const { settings, className, children } = this.props;
    const SliderRendered = dynamic(import('react-slick'), {
      ssr: this.state.isServer
    });

    return (
      <SliderRendered className={classNames('Slider', className)} {...settings}>
        {children}
      </SliderRendered>
    );
  }
}

export default Slider;

...or with hooks:

import React, { useState, useEffect } from 'react';
import dynamic from 'next/dynamic';
import classNames from 'classnames';

const Slider = ({ settings, className, children }) => {
  const [isServer, setServerState] = useState(true);
  const SliderRendered = dynamic(import('react-slick'), {
    ssr: isServer
  });
  useEffect(() => void setServerState(false), []);

  return (
    <SliderRendered className={classNames('Slider', className)} {...settings}>
      {children}
    </SliderRendered>
  );
};

export default Slider;
isBatak commented 5 years ago

This is my solution:

import React, { PureComponent } from 'react';
import SlickSlider from 'react-slick';

class Slider extends PureComponent {
  state = {
    isClient: false
  };

  componentDidMount() {
    this.setState((state) =>  { isClient: true });
  }

  render() {
    const {
        children,
        responsive,
        ...rest
    } = this.props;
    const { isClient } = this.state;

    return (
      <SlickSlider 
          key={isClient ? 'client' : 'server'}
          responsive={isClient ? responsive : null}
          {...rest}
      >
        {children}
      </SlickSlider>
    );
  }
}

export default Slider;
romainquellec commented 5 years ago

Well, I have this bug but I don't want a state for this. Is @akiran is willing to fix that ? Thanks.

rubenkuipers commented 4 years ago

@akiran any updates regarding this issue?

DB-Alex commented 4 years ago

@akiran any updates?

silksil commented 4 years ago

This is my solution:

import React, { PureComponent } from 'react';
import SlickSlider from 'react-slick';

class Slider extends PureComponent {
  state = {
    isClient: false
  };

  componentDidMount() {
    this.setState((state) =>  { isClient: true });
  }

  render() {
    const {
        children,
        responsive,
        ...rest
    } = this.props;
    const { isClient } = this.state;

    return (
      <SlickSlider 
          key={isClient ? 'client' : 'server'}
          responsive={isClient ? responsive : null}
          {...rest}
      >
        {children}
      </SlickSlider>
    );
  }
}

export default Slider;

This is my solution:

import React, { PureComponent } from 'react';
import SlickSlider from 'react-slick';

class Slider extends PureComponent {
  state = {
    isClient: false
  };

  componentDidMount() {
    this.setState((state) =>  { isClient: true });
  }

  render() {
    const {
        children,
        responsive,
        ...rest
    } = this.props;
    const { isClient } = this.state;

    return (
      <SlickSlider 
          key={isClient ? 'client' : 'server'}
          responsive={isClient ? responsive : null}
          {...rest}
      >
        {children}
      </SlickSlider>
    );
  }
}

export default Slider;

Thanks for this solution @isBatak !

I can't quit figure out exactly in my head what happens here; the images are still being rendered on server-side but it works because .......?

StarpTech commented 4 years ago

Hi, I had the same issue. The workaround works great.

@silksil I think it works because when we use different keys react won't hydrate the state from the server but creates it from scratch on the client.

Setting responsive to null on server-side prevents from creating elements that are created on client side afterward.

@akiran any hint how we can fix it?

isBatak commented 4 years ago

Hi @silksil, that trick is called two-pass rendering, you can find more info here https://reactjs.org/docs/react-dom.html#hydrate. In a nutshell, it will trigger second render on the client.

  1. render on server side (there is no window so media query detection won't work)
  2. hydration pass on client (window is available but if we change DOM in this phase hydration will fail)
  3. trigger second render on client by changing the key (in this phase DOM changes won't be a problem)

Overall, this problem can't be solved with JS because there is no window on server side and there is no way to detect window width. Only way to solve it is to generate pure CSS media queries and never touch the DOM structure.

norbiu commented 4 years ago

Any updates?

arun-reddy-g commented 4 years ago

@akiran any update on the issue ?

impe93 commented 4 years ago

Inspired by the @isBatak solution, mine is using react hooks:

import { useState, useEffect } from 'react';
import SlickSlider from 'react-slick';

const Slider = ({ children, responsive, ...rest}) => {
  const [isClient, setIsClient] = useState(false);

  useEffect(() => {
    setIsClient(true);
  }, [])

  return (
    <SlickSlider 
        key={isClient ? 'client' : 'server'}
        responsive={isClient ? responsive : null}
        {...rest}
    >
      {children}
    </SlickSlider>
  );

}

export default Slider;
leonace924 commented 4 years ago
import React from "react";
import Slider from "react-slick";

const settings = {
    dots: false,
    infinite: false,
    speed: 500,
    slidesToShow: 4,
    slidesToScroll: 1,
    lazyLoad: false,
    arrows: false,
    responsive: [
      {
        breakpoint: 1280,
        settings: {
          slidesToShow: 3.125,
        }
      },
      {
        breakpoint: 992,
        settings: {
          slidesToShow: 2.125,
        }
      },
      {
        breakpoint: 680,
        settings: {
          slidesToShow: 1.75,
        }
      },
  ]
}

If I use this for SSR, it gives me warning like these two

Warning: Prop `style` did not match. Server: "width:100%;left:0%" Client: "width:128%;left:0%"
Warning: Prop `className` did not match. Server: "slick-slide slick-active" Client: "slick-slide"

What is the solution for this warning?

leonace924 commented 4 years ago

Hello, @rubenkuipers To be honest, I need above thing urgently, I think that maybe react-slick has issue or I missed one option for SSR.

It'd be great if you advice me. Thank you

dpkpaa commented 3 years ago

If you are using next js then just use dynamic import for that component where you used react-slick or if you are not using ssr then just use lazyload component and suspension .

mayanksainsburys commented 3 years ago

Tried using dynamic import const SliderRendered = dynamic(import('react-slick')); and <SliderRendered {...settings} but still mixes the content! Please help

dpkpaa commented 3 years ago

import Slider from "react-slick" const ExampleComponent = () => { return ( <SliderRendered {...settings}> //your items ); }

use below example when you are importing your slider component to page const ExampleComponent = dynamic(import('./ExampleComponent'));

supriome commented 3 years ago

There have same problem when use swiper/react in nextjs with responsive mode

anettwu commented 3 years ago
// component swiper
import { Swiper, SwiperSlide } from 'swiper/react';
import SwiperCore, { Navigation, Pagination } from 'swiper';
import 'swiper/swiper.min.css';
import 'swiper/components/pagination/pagination.min.css';
SwiperCore.use([Navigation, Pagination]);

<Swiper
navigation={{
    nextEl: '.next',
    prevEl: '.prev',
}}
breakpoints={{
    1000: {
        slidesPerView: 3,
    },
    0: {
        slidesPerView: 1,
    },
}}>
{data.map((item, index) => (
    <SwiperSlide key={index}>
        <article>
            <img src={item.thumbnailUrl} />
            <h2>{item.id}</h2>
        </article>
    </SwiperSlide>
))}
</Swiper>

//----------------------------------------------------
// index.js

const DynamicComponent = dynamic(() => import('../components/Swiper'), {
    ssr: false,
});

export default function Home({ data }) {

    return (
        <div>
            <DynamicComponent data={data} />
        </div>
    );
}

export async function getServerSideProps() {
    const res = await fetch(
        `https://jsonplaceholder.typicode.com/photos?_limit=10`
    );
    const data = await res.json();
    return { props: { data } };
}
Commondore commented 3 years ago

@anettwu Thanks for the hint

AmirAmArA commented 9 months ago

+1

AmirAmArA commented 9 months ago

next js 14 not working , facing hydration problems