DEV Community

Cover image for How to create a circle countdown (React component)
Sergei Ipatev
Sergei Ipatev

Posted on • Edited on

How to create a circle countdown (React component)

Once I needed to create a countdown component in the form of a circle and decided to share the implementation:

Stack: ReactJS, TypeScript, scss(modules).

The first step needed to create a component(CircleCountDown.tsx):

import { useEffect, useState, FC, useMemo } from 'react';
import styles from './CircleCountDown.module.scss';

interface CircleCountDownProps {
  time: number;
  size: number;
  stroke: string;
  strokeWidth: number;
  onComplete?: VoidFunction;
  strokeLinecap?: 'butt' | 'round' | 'square' | 'inherit' | undefined;
}

const CircleCountDown: FC<CircleCountDownProps> = ({
  time,
  size,
  stroke,
  onComplete,
  strokeWidth,
  strokeLinecap = 'round',
}) => {
  const radius = size / 2;
  const milliseconds = time * 1000;
  const circumference = size * Math.PI;

  const [countdown, setCountdown] = useState(milliseconds);

  const seconds = (countdown / 1000).toFixed();

  const strokeDashoffset = circumference - (countdown / milliseconds) * circumference;

  useEffect(() => {
    const interval = setInterval(() => {
      if (countdown > 0) {
        setCountdown(countdown - 10);
      } else {
        clearInterval(interval);
        onComplete && onComplete();
      }
    }, 10);
    return () => clearInterval(interval);
  }, [countdown]);

  return (
    <div className={styles.root}>
      <label className={styles.seconds}>{seconds}</label>
      <div className={styles.countDownContainer}>
        <svg className={styles.svg} width={size} height={size}>
          <circle
            fill="none"
            r={radius}
            cx={radius}
            cy={radius}
            stroke={stroke}
            strokeWidth={strokeWidth}
            strokeLinecap={strokeLinecap}
            strokeDasharray={circumference}
            strokeDashoffset={strokeDashoffset}
          />
        </svg>
      </div>
    </div>
  );
};

export default CircleCountDown;
Enter fullscreen mode Exit fullscreen mode

The second step is to create styles(CircleCountDown.module.scss):

@mixin font-serif($name, $size: false, $color: false, $fontStyle: false, $lineHeight: false, $weight: false) {
  font-family: $name, sans-serif;
  @if $size {
    font-size: $size;
  }
  @if $color {
    color: $color;
  }
  @if $fontStyle {
    font-style: $fontStyle;
  }
  @if $lineHeight {
    line-height: $lineHeight;
  }
  @if $weight {
    font-weight: $weight;
  }
}

.root {
  display: flex;
  justify-content: center;
  align-items: center;
  position: relative;
}

.seconds {
  position: absolute;
  @include font-serif('Roboto', 16px, false, normal, 20px, 500);
}

.svg {
  transform: scale(-1, 1);
  overflow: visible;
}

.countDownContainer {
  transform: rotate(90deg);
}
Enter fullscreen mode Exit fullscreen mode

The third step is to use(App.tsx):

import React from 'react';
import CircleDownCircle from "./components/CircleCountDown/CircleCountDown";

function App() {
  return (
    <CircleDownCircle time={114} size={45} stroke="#000" strokeWidth={2} />
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

As you can see, we have the method onComplete. When the timer finished onComplete will call.
Well, that's all. I hope you like this example ^^)

Source code: Github

Live Demo:

Top comments (0)