How to build a horizontal date-picker
Tags: react, webdev
Published: 2023-04-25 00:21:35

Imagine the challenge of building a horizontal date-picker from scratch as a developer intern. It seemed like an impossible task, with little guidance available online. But with perseverance and creativity, I cracked the code and successfully built a sleek and functional horizontal date picker.

In this guide, I will share my hard-earned insights and step-by-step approach on how I tackled this daunting project using React and Tailwind CSS. So, fasten your seatbelt and get ready to embark on an exciting journey to create a horizontal date picker that will impress your users.

Step 1: Create a new React app Open your terminal in VSCode and run the following command to create a new React app using Create React App:

```
npx create-react-app horizontal-date-picker
horizontal-date-picker
```
Step 2: Install Tailwind CSS Next, we need to install Tailwind CSS, which is a popular CSS framework that we’ll use to style our date picker. Run the following command in your terminal:

```
npm install -D tailwindcss
npx tailwindcss init
```
Step 3: Configure Tailwind CSS Open the tailwind.config.js file and configure it like this.

```
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./src/**/*.{js,jsx,ts,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}
```
This will create a default configuration file for Tailwind CSS in your app’s root directory.

Step 4: Import Tailwind CSS into your app To use Tailwind CSS in your React app, you need to import its CSS file. Open the src/index.js file in your app and add the following line at the top:

```
@tailwind base;
@tailwind components;
@tailwind utilities;
```

## Main Component
Now that we have all the dependencies installed and ready, let’s dive right into the implementation.

Start by navigating to your ‘src’ folder in your project directory and create a file called ‘Calendar’. Inside this file, we’ll begin building our horizontal date picker.”

```
import React, { useLayoutEffect, useState } from 'react'
import styled from "styled-components";
import leftArrowDate from "./assets/left-arrow-date.svg";

function Calendar() {
const [dateList, setDateList] = useState([]);
  const [day, setDay] = useState({});
  const [intervalsLeft, setIntervalsLeft] = useState([]);
  const [intervalsRight, setIntervalsRight] = useState([]);
  const [intervalIdLeft, setIntervalIdLeft] = useState();
  const [intervalIdRight, setIntervalIdRight] = useState();

const toDayName = (n) => {
    switch (n) {
      case 0:
        return "Monday";
      case 1:
        return "Tuesday";
      case 2:
        return "Wednesday";
      case 3:
        return "Thursday";
      case 4:
        return "Friday";
      case 5:
        return "Saturday";
      case 6:
        return "Saunday";
    }
  };

  const toMonthName = (n) => {
    switch (n) {
      case 1:
        return "JAN";
      case 2:
        return "FEB";
      case 3:
        return "MAR";
      case 4:
        return "APR";
      case 5:
        return "MAY";
      case 6:
        return "JUN";
      case 7:
        return "JUL";
      case 8:
        return "AUG";
      case 9:
        return "SEP";
      case 10:
        return "OCT";
      case 11:
        return "NOV";
      case 12:
        return "DEC";
    }
  };

 const generateDateList = () => {
    let date = new Date();
    let newDateList = [];
    for (let i = 0; i < 90; i++) {
      newDateList.push({
        number: date.getDate(),
        day: toDayName(date.getDay()),
        month: date.getMonth() + 1,
        year: date.getFullYear(),
      });
      date.setDate(date.getDate() + 1);
    }
    return newDateList;

 useLayoutEffect(() => {
    const newDateList = generateDateList();
    setDateList(newDateList);
    setDay(newDateList);
  }, []);
  };

  return (
    <div>
      
    </div>
  )
}

export default Calendar
```
Basically, this is a React component called “Calendar” that generates a list of dates for the next 90 days using the JavaScript “Date” object. It pushes date objects with day, month, year, and day of the week information into an array. The component returns an empty div element and is exported for use in other parts of the application.

Inside the loop, the function pushes an object into the “newDateList” array. This object contains four properties:

1. “number” — which represents the day of the month using the “getDate()” method of the “Date” object.

2. “day” — which represents the name of the day of the week using the “getDay()” method of the “Date” object, and passing it to a helper function called “toDayName()” to get the name of the day.

3.“month” — which represents the current month using the “getMonth()” method of the “Date” object, and adding 1 to it as JavaScript months are zero-indexed.

4.“year” — which represents the current year using the “getFullYear()” method of the “Date” object.

5. The useLayoutEffect hook is used to perform an effect after the component is initially rendered and laid out. It generates a new list of dates, updates the state variables dateList and day with the new values, and triggers a re-render of the component. It only runs once during the component's lifecycle due to the empty dependency array [] passed as the second argument.

6.The component also defines two helper functions toDayName and toMonthName that convert numeric values representing days of the week and months to their corresponding names.

## Render component
Next, let’s add the following code inside the div element of the 'Calendar' component we created earlier: 

```
<DateContainer id="scroll">
          {dateList.map((d, index) => (
            <div
              key={index}
              className="flex flex-col justify-center items-center font-bold"
            >
              <span className="text-[13px] leading-10">{d.day[0]}</span>
              <div className="w-[40px] m-0 p-0 h-0 border-2 border-purple-400" />
              <div
                onClick={() => setDay(d)}
                className={`rounded-full ${
                  day.number === d.number &&
                  day.month === d.month &&
                  day.year === d.year
                    ? "bg-purple-500"
                    : "bg-transparent"
                } w-[30px] h-[20px] flex justify-center items-center cursor-pointer my-3 0 4`}
              >
                <span className="text-xs leading-40">
                  {("0" + d.number).slice(-2)}
                </span>
              </div>
              <span
                className={`text-xs ${
                  day.number === d.number &&
                  day.month === d.month &&
                  day.year === d.year
                    ? "font-bold text-purple-600"
                    : "font-medium text-purple-400"
                } ${
                  d.number === 1 ||
                  index === 0 ||
                  (day.number === d.number &&
                    day.month === d.month &&
                    day.year === d.year)
                    ? "visible"
                    : "invisible"
                }`}
              >
                {toMonthName(d.month)}
              </span>
            </div>
          ))}
        </DateContainer>

<--- make sure Datacontainer style is not placed inside the Calender component --->

const DateContainer = styled.div`
  height: 130px;
  color: #727070;
  display: flex;
  justify-content: left;
  flex-wrap: nowrap;
  overflow-x: scroll;
  width: 80%;
  ::-webkit-scrollbar {
    display: none;
  }
`;

<--- make sure Datacontainer style is not placed inside the Calender component --->
```

Here’s a summarized and comprehensive overview of what’s happening:

1. DateContainer component is being used with an id of "scroll", which may indicate that it is meant to be a scrollable container for the date list.
2. The dateList array is being iterated using .map() function to generate a list of date elements.
3. Each date element is wrapped in a <div> element with the flex, flex-col, justify-center, and items-center classes, which aligns its children in a column with vertical and horizontal center alignment.
4. Overall, the code generates a list of date elements with specific styles and behaviors based on the day and d objects' properties, and uses Tailwind CSS classes to define the styling and layout of the date container and its children elements.

## Navigation Arrow

```
 const autoScroll = (direction) => {
    if (direction === "left") {
      setIntervalIdLeft(
        setInterval(() => {
          document.getElementById("scroll")?.scrollBy(-3, 0);
        }, 5)
      );
      setIntervalsLeft([...intervalsLeft, intervalIdLeft]);
    } else {
      setIntervalIdRight(
        setInterval(() => {
          document.getElementById("scroll")?.scrollBy(3, 0);
        }, 5)
      );
      setIntervalsRight([...intervalsRight, intervalIdRight]);
    }
  };
  const clearRunningIntervals = () => {
    intervalsLeft.map((i) => {
      clearInterval(i);
      return null;
    });
    intervalsRight.map((i) => {
      clearInterval(i);
      return null;
    });
  };
```

In this code snippet, there are two functions and an image element with event handlers.

1. autoScroll: This function takes a direction ("left" or "right") as an argument and sets up an interval using setInterval() to repeatedly scroll a DOM element with an id of "scroll" in the specified direction (-3 pixels for "left" and 3 pixels for "right") every 5 milliseconds. The interval IDs are stored in state variables intervalIdLeft and intervalIdRight using respective setter functions. The current intervals IDs are also appended to intervalsLeft or intervalsRight arrays using setIntervalsLeft or setIntervalsRight setter functions.
2. clearRunningIntervals: This function is called to clear all the intervals stored in intervalsLeft and intervalsRight arrays using clearInterval() function.

## Navigation Implementation

```
{window.innerWidth > 10 ? (
          <img
            src={leftArrowDate}
            alt="left"
            style={{
              position: "absolute",
              left: "15px",
              height: "15px",
              cursor: "pointer",
            }}
            id="scroll-left"
            // onClick={() => autoScroll("left")}
            onMouseDown={() => autoScroll("left")}
            onMouseUp={() => {
              clearInterval(intervalIdLeft);
              clearRunningIntervals();
            }}
            onDrag={() => {
              clearInterval(intervalIdLeft);
            }}
          />
        ) : null}

<-- <DataContainer should be placed here --->

{window.innerWidth > 10 ? (
          <img
            src={leftArrowDate}
            alt="right"
            style={{
              position: "absolute",
              right: "15px",
              height: "15px",
              transform: "rotate(180deg)",
              cursor: "pointer",
            }}
            // onClick={() => autoScroll("right")}
            onMouseDown={() => autoScroll("right")}
            onMouseUp={() => {
              clearInterval(intervalIdRight);
              clearRunningIntervals();
            }}
            onDrag={() => {
              clearInterval(intervalIdRight);
            }}
          />
        ) : null}
```

In this code snippet, there is an image element with event handlers.

1. The image element: This image is conditionally rendered based on the inner width of the window being greater than 10 pixels. It has event handlers attached for onMouseDown, onMouseUp, and onDrag events. These event handlers trigger the autoScroll and clearRunningIntervals functions with different parameters to control the scrolling behavior. The image has specific styles for a position, left offset, height, and cursor. If the inner width of the window is not greater than 10 pixels, the image is not rendered (null).


Here is the expected outcome:


![horizontal datapicker](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hctt3omotb9f5mgwo3y8.png)

Thank you for taking the time to read my article. I hope you find it valuable and useful. If you have any further questions or need additional information, please don’t hesitate to ask. Thank you for your support!