React Hooks: useSlider
Tags: react, reacthooks, javascript
Published: 2020-01-02 06:34:25

---
title: React Hooks: useSlider
published: true
date: 2019-09-11 00:00:00 UTC
tags: react, react hooks, javascript
canonical_url: https://joelmturner.com/blog/react-hooks-useslider
description: A handy slider using React hooks
cover_image: https://joelmturner.com/static/9f00f7900fb59aea8fbf2784635d7d75/cefe4/react-hooks-useslider.webp
---

We needed an auto-advancing image slider in React. I chose to use hooks for this feature. This hook leverages a `useInterval` hook from [Dan Abrimov](https://overreacted.io/making-setinterval-declarative-with-react-hooks/).

## Requirements

This component needs to do a few things.

- [] Should accept an array of slides
- [] Should accept a duration in milliseconds
- [] Should animate between slides
- [] Should move through array on its own

## useInterval

Here's the `useInterval` code.

```jsx
import React, { useState, useEffect, useRef } from 'react';

function useInterval(callback, delay) {
  const savedCallback = useRef();

  // Remember the latest callback.
  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  // Set up the interval.
  useEffect(() => {
    function tick() {
      savedCallback.current();
    }
    if (delay !== null) {
      let id = setInterval(tick, delay);
      return () => clearInterval(id);
    }
  }, [delay]);
}
```

Setting an interval can be problematic in JavaScript, mostly because of cleanup (or lack of). With `useEffect` we get a nice cleanup with the return function, `return () => clearInterval(id);`. 

## useSlider

Now that we have that set up we can leverage it to help us with timing. 

```jsx
import * as React from 'react';
import useInterval from './UseInterval';

function useSlider({
    total = 0, // the length of the slide array
    enabled = false, // pauses/disables the player
    useLoaded = false, // this allows for delayed loads like images or embeds
    speed = 1000, // speed in milliseconds to show each slide
    loop = true, // should it start back at the beginning
}) {
    const [offset, setOffset] = React.useState(0);
    const [items, setItems] = React.useState([]);

    function incrementOffset() {
        if (offset === total - 1) {
            setOffset(loop ? 0 : offset);
        } else {
            setOffset(offset + 1);
        }
    }

    function addItem(ref) {
        setItems([...items, ref]);
    }

    const loaded = useLoaded ? items.length === total : true;

    useInterval(() => {
        if (loaded && enabled && offset < total) {
            incrementOffset();
        }
    }, speed);

    return {
      offset, // this is the current index of the slider
      addItem // this takes a ref and adds it to the items array to see if all have loaded
    };
}

export default useSlider;
```

## Slider Component

Our slider component adds all slides next to each other and moves the `.scroller` (absolutely positioned) through the `.container` (relatively positioned). This allows us to animate between the slides. Here’s the stateless structure of our component.

```css
.container {
  background-color: #ccc;
  margin: 0 auto;
  position: relative;
  overflow: hidden;
}

.scroller {
  position: absolute;
  transition: transform 350ms;
  height: 100%;
  display: flex;
}

.slide {
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  transition: opacity 350ms;
}
```

```jsx
import React from "react";
import useSlider from "./useSlider";

const slides = [
  {
    title: "Slide 1",
    color: "#56777A"
  },
  {
    title: "Slide 2",
    color: "#84ACAC"
  },
  {
    title: "Slide 3",
    color: "#FBA434"
  }
];

function Slider() {

  const slideWidth = 300;

  return (
    <div
      className="container"
      style={{
        backgroundColor: slide.color,
        width: slideWidth,
        height: slideWidth
      }}
      >
      <div
        className="scroller"
        style={{
          // our counter offset will go here
          transform: `translate3d(-${offset * slideWidth}px,0,0)`,
          width: `${slides.length * slideWidth}px`
        }}
      >
        {slides.map((slide, index) => (
          <div
            key={slide.title}
            className="slide"
            style={{
              backgroundColor: slide.color,
              width: slideWidth,
              height: slideWidth
            }}
          >
            {slide.title}
          </div>
        ))}
      </div>
    </div>
  );
}
```

## Putting it All Together

Now we can add our hook to our slider component. This will give us all the states that we’ll need for this feature. When it's all together we get a slider that moves the slides horizontally and rewinds after the last one. You can hook up the slider props to manage the slider options if needed. It can also be made to go vertical with a little modification.

{% codesandbox simple-react-hooks-interval-slider-vsmi1 %}

Cool! Requirements met.
- [x] Should accept an array of slides
- [x] Should accept a duration in milliseconds
- [x] Should animate between slides
- [x] Should move through array on its own