0

I have a working piece of code (a customised version of MUI DatePicker component). I'm using document.querySelectorAll in 2 places in my code. I'm asked to remove the usage of query selectors from the code as it's a bad practice. Is there a way. Code is attached for reference

Note: its being used in the useEffect and 2 util functions at the end

import {
  type Action,
  useRegisterBindings,
} from "@ui/react-framework";
import { Form as AntForm } from "antd";
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
import { useState, useCallback, useEffect, useRef } from "react";
import dayjs from "dayjs";
import { isEmpty, isEqual } from "lodash-es";
import { WidgetRegistry } from "../../registry";
import type { WidgetProps } from "../../shared/types";
import { useAction } from "../../runtime/hooks/useAction";
import type { FormInputProps } from "../Form/types";
import { DateDisplayFormat } from "../Date/utils/DateConstants";
import { DatePickerContext } from "../Date/utils/DatePickerContext";
import { isDateValid } from "../Date/utils/isDateValid";
import { CalendarHeader } from "./CalendarHeader";
import { ActionBar } from "./ActionBar";

type DateProps = {
  initialValue?: string;
  firstDate?: string;
  lastDate?: string;
  showCalendarIcon?: boolean;
  onChange?: Action;
} & WidgetProps &
  FormInputProps<string>;

export const DateRange: React.FC<DateProps> = (props) => {
  const [startDate, setStartDate] = useState<string>("");
  const [endDate, setEndDate] = useState<string>("");
  const [value, setValue] = useState<dayjs.Dayjs | string | undefined>("");
  const [openPicker, setOpenPicker] = useState(false);
  const [isCalendarOpen, setIsCalendarOpen] = useState(false);
  const [enteredDate, setEnteredDate] = useState("Start Date - End Date");
  const [errorText, setErrorText] = useState("");
  const [endDateErrorText, setEndDateErrorText] = useState("");
  const [calendarMonth, setCalendarMonth] = useState<number>(
    getCalendarMonth(),
  );
  const datePickerRef = useRef<HTMLDivElement | null>(null);
  const action = useAction(props.onChange);

  const { values } = useRegisterBindings({ ...props, value }, props.id, {
    setValue,
  });

  const onChangeCallback = useCallback(
    (date: string) => {
      if (!action) {
        return;
      }
      action.callback({
        [props?.id as string]: {
          value: date,
          setValue,
          ...props,
        },
      });
    },
    [action, props],
  );

  const onDateChange = (date: unknown): void => {
    if (!isCalendarOpen || !openPicker) {
      return;
    }

    const formattedDate = dayjs(date as string).format(DateDisplayFormat);
    if (formattedDate !== "Invalid Date") {
      if (
        isEmpty(startDate) ||
        (!isEmpty(startDate) && !isEmpty(endDate)) ||
        dayjs(date as string).isBefore(dayjs(startDate))
      ) {
        setStartDate(formattedDate);
        setEndDate("");
        setEnteredDate(`${formattedDate} - End Date`);
      } else {
        setEndDate(formattedDate);
        setEnteredDate(`${startDate} - ${formattedDate}`);
        onChangeCallback(formattedDate);
      }
    }
  };

  const onDateClear = (): void => {
    setValue(undefined);
    setStartDate("");
    setEndDate("");
    setEnteredDate("Start Date - End Date");
  };

  useEffect(() => {
    if (!isDateValid(startDate) && !isDateValid(endDate)) {
      return;
    }

    const startDay = parseInt(startDate.substring(3, 5));
    const endDay = parseInt(endDate.substring(3, 5));
    const startMonth = parseInt(startDate.substring(0, 2));
    const endMonth = parseInt(endDate.substring(0, 2));
    const startYear = parseInt(startDate.substring(6, 10));
    const endYear = parseInt(endDate.substring(6, 10));

    const calendarYear = getCalendarYear();
    const newCalendarMonth = getCalendarMonth();
    setCalendarMonth(newCalendarMonth);

    datePickerRef?.current
      ?.querySelectorAll(".MuiPickersDay-root")
      ?.forEach((button) => {
        const buttonDay = parseInt(button.textContent?.trim() || "");

        if (
          (isEqual(startMonth, newCalendarMonth) &&
            isEqual(buttonDay, startDay) &&
            isEqual(startYear, calendarYear)) ||
          (isEqual(endMonth, newCalendarMonth) &&
            isEqual(buttonDay, endDay) &&
            isEqual(endYear, calendarYear))
        ) {
          button.classList.add("CustomSelected");
        } else {
          button.classList.remove("CustomSelected");
        }

        if (
          (startYear < calendarYear ||
            (isEqual(startYear, calendarYear) &&
              startMonth < newCalendarMonth) ||
            (isEqual(startYear, calendarYear) &&
              isEqual(startMonth, newCalendarMonth) &&
              buttonDay > startDay)) &&
          (endYear > calendarYear ||
            (isEqual(endYear, calendarYear) && endMonth > newCalendarMonth) ||
            (isEqual(endYear, calendarYear) &&
              isEqual(endMonth, newCalendarMonth) &&
              buttonDay < endDay))
        ) {
          button.classList.add("CustomInRange");
        } else {
          button.classList.remove("CustomInRange");
        }
      });
  }, [startDate, endDate, calendarMonth, isCalendarOpen, openPicker]);

  return (
    <DatePickerContext.Provider
      value={{
        firstDate: props?.firstDate,
        lastDate: props?.lastDate,
        value,
        setValue,
        startDate,
        setStartDate,
        endDate,
        setEndDate,
        isCalendarOpen,
        setIsCalendarOpen,
        setOpenPicker,
        enteredDate,
        setEnteredDate,
        errorText,
        setErrorText,
        endDateErrorText,
        setEndDateErrorText,
      }}
    >
      <AntForm.Item
        label={values?.label}
        name={props.id ? values?.id : values?.label}
        rules={[{ required: values?.required }]}
        style={{
          margin: "0px",
          alignItems: "center",
        }}
      >
        <LocalizationProvider dateAdapter={AdapterDayjs}>
          <style>{customDateRangeStyles}</style>
          <DatePicker
            label={value?.toString() || props?.hintText || "Select a date"}
            value={value}
            disabled={props?.enabled === false}
            closeOnSelect={false}
            open={openPicker}
            maxDate={props.lastDate ? dayjs(props.lastDate) : undefined}
            minDate={props.firstDate ? dayjs(props.firstDate) : undefined}
            onClose={(): void => setOpenPicker(false)}
            onChange={onDateChange}
            onMonthChange={(date: unknown): void =>
              setCalendarMonth(dayjs(date as string | dayjs.Dayjs).month() + 1)
            }
            slots={{
              calendarHeader: CalendarHeader,
              actionBar: isCalendarOpen ? undefined : ActionBar,
            }}
            slotProps={{
              actionBar: isCalendarOpen
                ? undefined
                : {
                    actions: ["cancel", "accept"],
                  },
              field: {
                clearable: Boolean(value),
                onClear: onDateClear,
              },
              textField: {
                InputLabelProps: {
                  shrink: false,
                  sx: !isEmpty(value)
                    ? {
                        color: "black !important",
                        fontSize: "16px !important",
                      }
                    : undefined,
                },
                onClick:
                  props?.enabled === false
                    ? undefined
                    : (): void => setOpenPicker(true),
                error: false,
                color: "success",
                variant: "outlined",
              },
              popper: {
                ref: datePickerRef,
                modifiers: [
                  {
                    name: "flip",
                    enabled: false,
                  },
                ],
              },
              layout: {
                sx: {
                  "& .MuiDateCalendar-root": {
                    overflow: "visible !important",
                    height: "unset !important",
                    maxHeight: "unset !important",
                    backgroundColor: "rgb(239, 223, 240)",
                  },
                  "& .MuiPickersFadeTransitionGroup-root.MuiDateCalendar-viewTransitionContainer":
                    {
                      display: `${
                        isCalendarOpen ? "block" : "none"
                      } !important`,
                    },
                  "& .Mui-selected": {
                    backgroundColor: "transparent",
                    color: "rgba(0, 0, 0, 0.87)",
                  },
                },
              },
            }}
            sx={{
              "& .MuiInputBase-input.MuiOutlinedInput-input.MuiInputBase-inputAdornedEnd":
                {
                  visibility: "hidden !important",
                },
              "& .MuiDateCalendar-root": {
                backgroundColor: "rgb(239, 223, 240)",
                overflow: "visible !important",
                height: "unset !important",
                maxHeight: "unset !important",
              },
            }}
          />
        </LocalizationProvider>
      </AntForm.Item>
    </DatePickerContext.Provider>
  );
};

WidgetRegistry.register("DateRange", DateRange);

const customDateRangeStyles = `
  .CustomSelected {
    background-color: rgb(81, 177, 145) !important;
    color: white !important;
  }
  .CustomInRange {
    background-color: rgb(208, 229, 253) !important;
  }
`;

const getCalendarMonth = (): number =>
  parseInt(
    new Date(
      `${
        document
          .querySelector(".MuiPickersCalendarHeader-label")
          ?.textContent?.split(" ")[0]
          .trim() || ""
      } 01 2000`,
    ).toLocaleDateString(`en`, {
      month: `2-digit`,
    }),
  );

const getCalendarYear = (): number =>
  parseInt(
    document
      .querySelector(".MuiPickersCalendarHeader-label")
      ?.textContent?.split(" ")[1]
      .trim() || "",
  );

0

Browse other questions tagged or ask your own question.