Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prev and Next button is not working correctly at the breakpoints

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;
like image 281
John Chucks Avatar asked Jul 17 '20 11:07

John Chucks


Video Answer


1 Answers

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.

like image 186
Narigo Avatar answered Nov 14 '22 23:11

Narigo