Joel M. Turner

Illustration

Blog

Notes

React Hooks: useSlider

We had a need for an auto advancing image slider in React. I chose to use hooks for this feature. This hook leverages a useInterval hook from Dan Abrimov.

Requirements

This component needs do a few things.

  • Should accept an slide array
  • Should accept a duration in milliseconds
  • Should animate between slides
  • Should move through array on it’s own

useInterval

Here’s the useInterval code.

1import React, { useState, useEffect, useRef } from 'react';
2
3function useInterval(callback, delay) {
4 const savedCallback = useRef();
5
6 // Remember the latest callback.
7 useEffect(() => {
8 savedCallback.current = callback;
9 }, [callback]);
10
11 // Set up the interval.
12 useEffect(() => {
13 function tick() {
14 savedCallback.current();
15 }
16 if (delay !== null) {
17 let id = setInterval(tick, delay);
18 return () => clearInterval(id);
19 }
20 }, [delay]);
21}

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);.

And with TypeScript.

useSlider

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

1import * as React from 'react';
2import useInterval from './UseInterval';
3
4function useSlider({
5 total = 0, // the length of the slide array
6 enabled = false, // pauses/disables the player
7 useLoaded = false, // this allows for delayed loads like images or embeds
8 speed = 1000, // speed in milliseconds to show each slide
9 loop = true, // should it start back at the beginning
10}) {
11 const [offset, setOffset] = React.useState(0);
12 const [items, setItems] = React.useState([]);
13
14 function incrementOffset() {
15 if (offset === total - 1) {
16 setOffset(loop ? 0 : offset);
17 } else {
18 setOffset(offset + 1);
19 }
20 }
21
22 function addItem(ref) {
23 setItems([...items, ref]);
24 }
25
26 const loaded = useLoaded ? items.length === total : true;
27
28 useInterval(() => {
29 if (loaded && enabled && offset < total) {
30 incrementOffset();
31 }
32 }, speed);
33
34 return {
35 offset, // this is the current index of the slider
36 addItem // this takes a ref and adds it to the items array to see if all have loaded
37 };
38}
39
40export 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.

1.container {
2 background-color: #ccc;
3 margin: 0 auto;
4 position: relative;
5 overflow: hidden;
6}
7
8.scroller {
9 position: absolute;
10 transition: transform 350ms;
11 height: 100%;
12 display: flex;
13}
14
15.slide {
16 display: flex;
17 align-items: center;
18 justify-content: center;
19 position: relative;
20 transition: opacity 350ms;
21}
1import React from "react";
2import useSlider from "./useSlider";
3
4const slides = [
5 {
6 title: "Slide 1",
7 color: "#56777A"
8 },
9 {
10 title: "Slide 2",
11 color: "#84ACAC"
12 },
13 {
14 title: "Slide 3",
15 color: "#FBA434"
16 }
17];
18
19function Slider() {
20
21 const slideWidth = 300;
22
23 return (
24 <div
25 className="container"
26 style={{
27 backgroundColor: slide.color,
28 width: slideWidth,
29 height: slideWidth
30 }}
31 >
32 <div
33 className="scroller"
34 style={{
35 // our counter offset will go here
36 transform: `translate3d(-${offset * slideWidth}px,0,0)`,
37 width: `${slides.length * slideWidth}px`
38 }}
39 >
40 {slides.map((slide, index) => (
41 <div
42 key={slide.title}
43 className="slide"
44 style={{
45 backgroundColor: slide.color,
46 width: slideWidth,
47 height: slideWidth
48 }}
49 >
50 {slide.title}
51 </div>
52 ))}
53 </div>
54 </div>
55 );
56}

Putting it All Together

Now we can add our hook to our slider component. This will give us all the state 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.

Cool! Requirements met.

  • Should accept an slide array
  • Should accept a duration in milliseconds
  • Should animate between slides
  • Should move through array on it’s own
Discuss this article on Twitter