How to build a DateCountdown in React?
Published: 2022-05-26 03:57:07

Recently I applied to a position as a Frontend developer and they asked me to solve a React challenge. Among other things, they asked to build a reusable DateCountdown component. If you're interested to see the challenge, check [this Reddit post](https://www.reddit.com/r/reactjs/comments/uxig9j/rate_this_code/i9yvk4d/?context=3).

Take a look at the **Takeaways section** at the end to learn some useful patterns I used here which can be useful in your projects.

## TL;DR
This is how the component will look at the end. Check the [sandbox](https://codesandbox.io/s/datecountdown-component-ed95kc?file=/src/components/DateCountdown/DateCountdown.helpers.ts) for the full code.

```jsx
const DateCountdown = ({ endDate }: DateCountdownProps) => {
  const { rDays, rHours, rMinutes, rSeconds } = useDateCountdown(endDate);

  return (
    <div className={styles.countdown}>
      <div className={styles.clock}>
        <div className={styles.ticker}>{rDays}</div>
        <div className={styles.label}>Days</div>
      </div>
      <Separator />
      <div className={styles.clock}>
        <div className={styles.ticker}>{rHours}</div>
        <div className={styles.label}>Hours</div>
      </div>
      <Separator />
      <div className={styles.clock}>
        <div className={styles.ticker}>{rMinutes}</div>
        <div className={styles.label}>Minutes</div>
      </div>
      <Separator />
      <div className={styles.clock}>
        <div className={styles.ticker}>{rSeconds}</div>
        <div className={styles.label}>Seconds</div>
      </div>
    </div>
  );
}; 
```
```jsx
const endDate = new Date("2022-12-27T16:25:00");
() => <DateCountdown endDate={endDate} />
```
  
---
  
## Implementation details 💻
The code above is pretty much self explanatory so let's focus on the custom hook which is a little tricker.

```jsx
import { useEffect, useState } from "react";
import { getRemainingTime } from "./DateCountdown.helpers";

type State = {
  rDays: number;
  rHours: number;
  rMinutes: number;
  rSeconds: number;
};

const initialState: State = { rDays: 0, rHours: 0, rMinutes: 0, rSeconds: 0 };

const useDateCountdown = (endDate: Date) => {
  const [state, setState] = useState<State>(initialState);

  useEffect(() => {
    const remainingTime = getRemainingTime(endDate);
    setState(remainingTime);

    const intervalId = setInterval(() => {
      const remainingTime = getRemainingTime(endDate);
      setState(remainingTime);
    }, 1000);

    return () => clearInterval(intervalId);
  }, [endDate]);

  return state;
};

export { useDateCountdown };
```
Notice that I call the `getRemainingTime` function outside of the `setInterval` because the interval will call `getRemainingTime` just after 1sec and I want it to start asap.

To implement the `getRemainingTime` I added `date-fns` as a dependency to help with the date calculations.

```jsx
import {
  getDaysInMonth,
  getMonth,
  getYear,
  intervalToDuration
} from "date-fns";

export const getRemainingTime = (endDate: Date) => {
  const now = new Date();
  const difference = intervalToDuration({
    start: now,
    end: endDate
  });

  /**
   * As the hook is not returning the number of months and years,
   * it needs to aggreagate those values into the days counter
   */
  let numOfDays = difference.days || 0;
  let numOfMonths = (difference.months || 0) + (difference.years || 0) * 12;

  for (let i = 1; i <= numOfMonths; i++) {
    numOfDays += getDaysInMonth(new Date(getYear(now), getMonth(now) + i));
  }

  return {
    rDays: numOfDays,
    rHours: difference.hours || 0,
    rMinutes: difference.minutes || 0,
    rSeconds: difference.seconds || 0
  };
};
```
Notice that the `intervalToDuration` from `date-fns` returns the range between seconds to years however my custom hook just returns the range between seconds to days so I had to implement the logic to convert the remaining months and years to days.

The logic is pretty much it. I used CSS Modules to style.
```css
.countdown {
  display: flex;
  align-items: center;
}

.dots {
  margin: 0 0.5rem;
}

.dot {
  width: 3px;
  height: 3px;
  border-radius: 100%;
  background-color: #fff;
}

.dot + .dot {
  margin-top: 3px;
}

.clock {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  position: relative;
  background-color: rgba(119, 126, 144, 0.2);
  border-radius: 8px;
  width: 70px;
  height: 60px;
}

.ticker {
  font-size: 1rem;
  font-weight: 600;
}

.label {
  font-size: 0.75rem;
}

@media only screen and (min-width: 640px) {
  .dots {
    margin: 0 1rem;
  }

  .dot {
    width: 6px;
    height: 6px;
  }

  .dot + .dot {
    margin-top: 6px;
  }

  .clock {
    width: 100px;
    height: 90px;
  }

  .ticker {
    font-size: 1.5rem;
    font-weight: 600;
  }

  .label {
    font-size: 1rem;
    margin-top: 0.25rem;
  }
}
```

---

## Notes 📖
- At my original implementation [here](https://github.com/gabrielmlinassi/star-ledger-challenge) I didn't add the logic to convert months and years to days. I just realized that once I was writing this article 🙈

## Takeaways 🎯
Besides this code, I would like to call your attention to some good React patterns I assembled together here which can be useful to apply in other projects.

- **Use custom hooks**. They help you write clean code because it separates the view from the logic.
- **Keep closed related files in the same folder**. Notice that I nested some files inside of the DateCountdown folder because those are being used just by that component.

---

## Sandbox
{% embed https://codesandbox.io/embed/datecountdown-component-ed95kc?fontsize=14&hidenavigation=1&theme=dark %}