Create a Slideshow With React
Tags: react, slideshow, css, javascript
Published: 2021-07-01 20:08:07

Let's create a simple slideshow component today. It's easy and only take few steps to achieve.
Here is a demo and source code.
{% codesandbox ffmuk %}


In order to create a slideshow, we need to have two components:
* The container that holds contents.
* The individual slide.

## Container
For a slideshow, we need a container that can:
* contain all slides.
* hide slides that outside viewport of the container.
* sent a signal that tells slides to move in.
* sent a signal that tells slides to move out.
* track which slide should be shown.

First, let's create a basic container with style:
```typescript
export function Slideshow({ children, className, style }) {
  return (
      <div
        style={{
          height: "600px",
          width: "600px",
          position: "relative",
          overflow: "hidden"
        }}
      >
        {children}
      </div>
  );
}
```

Second, let's add a context that will track activated slide and a timer:
```typescript
const SlideshowContext = createContext();

export function Slideshow({ children, className, style }) {
  const [context, setContext] = useState({
    items: [],
    edge: false
  });
  const timer = useRef(null);

  useEffect(() => {
    if (timer.current) clearTimeout(timer.current);
    timer.current = setTimeout(() => {
      // Move deactivated slide out when edge is false
      // Move activated slide in when edge is true
      if (context.items.length > 1 && context.edge) {
        const head = context.items.shift();
        context.items.push(head);
      }
      context.edge = !context.edge;
      setContext({ ...context });
    }, 2500);

    return () => clearTimeout(timer.current);
  });

  console.log(context.items);

  return (
    <SlideshowContext.Provider value={[context, setContext]}>
      <div
        style={{
          height: "600px",
          width: "600px",
          position: "relative",
          overflow: "hidden"
        }}
      >
        {children}
      </div>
    </SlideshowContext.Provider>
  );
}
```
And that's all for the container.

## Slide

The slide will have at least three stages:
* on stage - slide is in viewport and has highest z-index.
* off stage - slide is in viewport and has lowest z-index
* ready for stage - slide is moved out from viewport, and it's waiting at ready position.

> The reason we don't move out the slide at "off stage", is because, for this slideshow, we want to move "off" slide after the "on" slide completely cover the "off" one.

Slide will update it's stage when received signal from the container, so it will like:
1. slide at ready position.
2. when signal come and the slide is activated, moved to viewport.
3. when signal come and the current slide is not the activated one, lower the z-index.
4. when next signal come, move to ready position.

So, we can have something like this:
```typescript
export function SlideshowItem({ children }) {
  const name = useRef(`${performance.now()}_${Math.random()}`);
// Generate a name for this slide.
  const [context] = useContext(SlideshowContext);
  const [stage, setStage] = useState("ready");

  useEffect(() => {
    // register self with the name.
    context.items.push(name.current);
    return () => {
      // Remove the name when slide is removed.
      const index = context.items.indexOf(name.current);
      context.items.splice(index, 1);
    };
  }, []);

  useEffect(() => {
    const activeName = context.items[0];
    if (activeName === name.current) {
      setStage("on");
    }
    if (activeName !== name.current && stage === "on") {
      setStage("off");
    }
    if (activeName !== name.current && stage === "off") {
      setStage("ready");
    }
  }, [context]);

  let left = 0;
  let zIndex = 0;
  switch (stage) {
    case "ready":
      left = "100%";
      break;
    case "on":
      left = "0";
      zIndex = 1;
      break;
    case "off":
      zIndex = 0;
      break;
    default:
  }

  return (
    <div
      style={{
        transition: "0.5s",
        position: "absolute",
        top: 0,
        left: left,
        zIndex: zIndex
      }}
    >
      {children}
    </div>
  );
}
```

And now, we have a simple Slideshow.
Thanks all!