8

Guys I'm facing a problem in react-slick slider. I'm rendering a card on the slider depends upon the array length. I'm also rendering custom next and previous buttons which triggers the next and previous functions to change the slide. I also wanted to this slider to be responsive. It means it has a functionality to scroll the slides according to the defined settings.

My defined settings for the breakpoints:

992 > slidesToScroll = 3

577 - 991 slidesToScroll = 2

0 - 576 slidesToScroll = 1

The next button will be disabled when there is no forward slide and the previous button will be disabled when there is no back slide. To achieve this functionality, there is a function named afterChange that returns the current active index of the slides. So I assign that index on the state. But I think my logic for the next button is not correct.

What are my issues?

  • Is there any way to refresh the slider when the window is resized because the slides are not reset according to the breakpoints?

  • The next button is not disabled at the breakpoint (0 - 576) even if there is no slide ahead.

  • When the user changes a single slide and then resizes the window to move to the next slide, the Next button becomes inactive.

I think the problem is in my logic that is written inside renderArrows function.

See this:

React-slick problem

Codesandbox: View this

Code:

import React, { useState, useRef } from "react";
import Slider from "react-slick";

// Constant Variables
// Slides scroll behavior on different sizes
const TOTAL_SLIDES = 6;
const DESKTOP_SLIDES_SCROLL = 3;
const TABLET_SLIDES_SCROLL = 2;
const MOBILE_SLIDES_SCROLL = 1;

/**
 * It will return the JSX and register the callbacks for next and previous slide.
 * @param prevCallback {function} - Go back to the previous slide
 * @param nextCallback {function} - Go to the next slide
 * @param state {object} - Holds the state of your slider indexes
 * @param totalSlides {number} - Holds the total number of slides
 * @return {*} - Returns the JSX
 */
const renderArrows = (
  prevCallback,
  nextCallback,
  { currentIndex, slidesToScroll },
  totalSlides
) => {
  const cycleDots =
    currentIndex === 0 ? 1 : Math.ceil(totalSlides / slidesToScroll);
  return (
    <div>
      <button disabled={currentIndex === 0} onClick={prevCallback}>
        Previous
      </button>
      <button disabled={currentIndex > cycleDots} onClick={nextCallback}>
        Next
      </button>
    </div>
  );
};

const App = () => {
  // We just have to keep track of the index by keeping it in the state
  const [state, setState] = useState({ currentIndex: 0, slidesToScroll: 0 });

  // To access underlying DOM object for the slider
  const sliderRef = useRef();

  // Trigger next method to show the next slides
  const next = () => {
    sliderRef.current.slickNext();
  };

  // Trigger previous method to show the previous slides
  const previous = () => {
    sliderRef.current.slickPrev();
  };

  // Slider Settings
  const settings = {
    slidesToShow: 3,
    dots: false,
    draggable: false,
    slidesToScroll: DESKTOP_SLIDES_SCROLL,
    arrows: false,
    speed: 1300,
    autoplay: false,
    centerMode: false,
    infinite: false,
    afterChange: indexOfCurrentSlide => {
      setState({
        currentIndex: indexOfCurrentSlide,
        slidesToScroll: DESKTOP_SLIDES_SCROLL
      });
    },
    responsive: [
      {
        breakpoint: 992,
        settings: {
          slidesToShow: 2,
          slidesToScroll: TABLET_SLIDES_SCROLL,
          afterChange: indexOfCurrentSlide => {
            setState({
              currentIndex: indexOfCurrentSlide,
              slidesToScroll: TABLET_SLIDES_SCROLL
            });
          }
        }
      },
      {
        breakpoint: 576,
        settings: {
          slidesToShow: 1,
          slidesToScroll: MOBILE_SLIDES_SCROLL,
          afterChange: indexOfCurrentSlide => {
            setState({
              currentIndex: indexOfCurrentSlide,
              slidesToScroll: MOBILE_SLIDES_SCROLL
            });
          }
        }
      }
    ]
  };

  return (
    <div className="app">
      <div>
        <h1>Slider Buttons</h1>
        {renderArrows(previous, next, state, TOTAL_SLIDES - 1)}
      </div>

      {/*  Slider */}
      <Slider {...settings} ref={slider => (sliderRef.current = slider)}>
        {[...Array(TOTAL_SLIDES)].map((_, index) => {
          return (
            <div className="card" key={index}>
              <div className="card-img-top">
                <svg
                  className="svg"
                  width="100%"
                  height="225"
                  xmlns="http://www.w3.org/2000/svg"
                  preserveAspectRatio="xMidYMid slice"
                  focusable="false"
                >
                  <rect width="100%" height="100%" fill="#55595c" />
                  <text x="50%" y="50%" fill="#eceeef" dy=".3em">
                    Image {index + 1}
                  </text>
                </svg>
              </div>
              <div className="card-body">
                <p className="card-text">
                  This is a wider card with supporting text below.
                </p>
              </div>
            </div>
          );
        })}
      </Slider>
    </div>
  );
};

export default App;
John Chucks
  • 381
  • 1
  • 15

3 Answers3

2

Looks like everything is working already but the disabled Next button. I think it should work already by changing the disabled condition to check the amount of slides the slider is at.

Changing the renderArrow body to this in the provided code sandbox seems like it works:

  const lastElementsIndex = totalSlides - slidesToScroll;
  return (
    <div>
      <button disabled={currentIndex === 0} onClick={prevCallback}>
        Previous
      </button>
      <button disabled={currentIndex > lastElementsIndex} onClick={nextCallback}>
        Next
      </button>
    </div>
  );

Since the initial state is not synchronized with the actual values depending on the breakpoint, there might be a special case as pointed out in the comments. Using onReInit as a non-arrow function should get access to the this object of the Slick slider itself. From there, the correct slidesToScroll can be retrieved:

const settings = {
  // ... other settings ...
  onReInit: function() {
    const slidesToScroll = this.slidesToScroll;
    if (slidesToScroll !== state.slidesToScroll) {
      setState(state => ({ ...state, slidesToScroll }));
    }
  },
  // ... other settings ...
};

In a previous edit, I've proposed using onInit - using onReInit has the benefit of shooting on window resize as well. Thus it should also synchronize the state correctly when resizing.

Narigo
  • 2,979
  • 3
  • 20
  • 31
  • Thanks for the answer. I want to ask if `currentIndex = 0`, `totalSlides = 2` and `slidesToScroll = 0`. So in this case `3 slides` have already appeared then `lastElementsIndex` value will be `2`. The next button won't be disabled because our condition will return false i.e. `0 > 2` How will you deal with it? – John Chucks Jul 26 '20 at 08:31
  • 1
    When `slidesToScroll = 0` - what does that mean? This sounds to me that the user should not be able to scroll at all / doesn't see any slides at the same time? – Narigo Jul 26 '20 at 13:09
  • 3
    I'm sorry I didn't understand you. This is the Codesandbox link [see here](https://codesandbox.io/s/react-slick-2-jsk7o-m0e8o) In this example you can see that the total slides in the global variables are `3` and in `Desktop break-point` 3 slides have already appeared and there is no slide ahead. So the next button should have been disabled. – John Chucks Jul 26 '20 at 14:38
  • Ahh okay, now I see the second issue. Sorry! In the linked code, `state` is not synchronized with the state that the slider receives in the beginning. `slidesToScroll` only updates to the correct value when the slider changes and thus updates later. You could use the `onInit` prop to synchronize that. I'll update the answer – Narigo Jul 26 '20 at 21:52
0

I find a problem in {currentIndex > cycleDots}. Please change into {currentIndex >= cycleDots}

Pradeep Saini
  • 680
  • 6
  • 17
0

Here is what I did to get your code working:

const renderArrows = (
  prevCallback,
  nextCallback,
  { currentIndex, slidesToScroll },
  totalSlides
) => {
  let cycleDots = 0;
  if (slidesToScroll !== 0) {
    cycleDots =
      currentIndex === 0 ? 1 : Math.ceil(totalSlides / slidesToScroll);
  } else {
    cycleDots = 5;
  }

  return (
    <div>
      <button disabled={currentIndex === 0} onClick={prevCallback}>
        Previous
      </button>
      <button disabled={currentIndex >= cycleDots} onClick={nextCallback}>
        Next
      </button>
    </div>
  );
};

I don't know what you wan't to set the 'cycleDots' value to in the if-else statement; but you should be able to replace the 5 with whatever the totalSlides is minus 1;

Solomon Bush
  • 165
  • 9