react-slick icon indicating copy to clipboard operation
react-slick copied to clipboard

SSR and responsive mode

Open padupuy opened this issue 7 years ago • 33 comments

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

  • OS: macOS High Sierra 10.13.4
  • Browser (if applies) : at least chrome and safari
  • Version of Next.js: at least 5.1 and 6.0.2

padupuy avatar May 28 '18 12:05 padupuy

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

virajsinha avatar Jun 07 '18 06:06 virajsinha

Is any solution to prevent this bug? Thank you.

masterGennadij avatar Jun 15 '18 15:06 masterGennadij

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>

davidsanchez96 avatar Jun 26 '18 14:06 davidsanchez96

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

tedlin182 avatar Jun 26 '18 17:06 tedlin182

+1

AndriiUhryn avatar Oct 30 '18 13:10 AndriiUhryn

+1

felipetoffolo1 avatar Nov 02 '18 17:11 felipetoffolo1

+1

josheche avatar Nov 20 '18 22:11 josheche

+1

camwes avatar Nov 21 '18 02:11 camwes

Thank you @davidsanchez96

xmanzero avatar Dec 03 '18 19:12 xmanzero

+1

lucanovera avatar Dec 21 '18 13:12 lucanovera

+1

goshdarnheck avatar Jan 23 '19 17:01 goshdarnheck

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;

chaance avatar Jan 31 '19 16:01 chaance

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;

isBatak avatar Feb 13 '19 15:02 isBatak

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

romainquellec avatar Jun 24 '19 15:06 romainquellec

@akiran any updates regarding this issue?

rubenkuipers avatar Oct 04 '19 12:10 rubenkuipers

@akiran any updates?

VegtMedia-Alex avatar Nov 08 '19 10:11 VegtMedia-Alex

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 .......?

silksil avatar Nov 21 '19 09:11 silksil

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?

StarpTech avatar Nov 24 '19 14:11 StarpTech

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.

isBatak avatar Nov 24 '19 17:11 isBatak

Any updates?

norbiu avatar Apr 01 '20 15:04 norbiu

@akiran any update on the issue ?

arun-reddy-g avatar Apr 29 '20 17:04 arun-reddy-g

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;

impe93 avatar Jun 25 '20 16:06 impe93

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 avatar Jul 21 '20 08:07 leonace924

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

leonace924 avatar Jul 21 '20 08:07 leonace924

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 .

dpkpaa avatar Oct 06 '20 05:10 dpkpaa

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

mayanksainsburys avatar Feb 10 '21 13:02 mayanksainsburys

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

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

dpkpaa avatar Feb 11 '21 03:02 dpkpaa

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

supriome avatar Mar 04 '21 10:03 supriome

// 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 } };
}

anettwu avatar May 22 '21 19:05 anettwu

@anettwu Thanks for the hint

Commondore avatar Jun 02 '21 04:06 Commondore