Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pickers] How to migrate custom DateRangePicker from v5 to v6? #12057

Closed
lildesert opened this issue Feb 14, 2024 · 5 comments
Closed

[pickers] How to migrate custom DateRangePicker from v5 to v6? #12057

lildesert opened this issue Feb 14, 2024 · 5 comments
Labels
component: pickers This is the name of the generic UI component, not the React module! support: commercial Support request from paid users support: pro standard Support request from a Pro standard plan user. https://mui.com/legal/technical-support-sla/ support: question Community support but can be turned into an improvement

Comments

@lildesert
Copy link

lildesert commented Feb 14, 2024

The problem in depth

Hey there!

I started to work on a migration from mui-x v5 to mui-x v6 and I'm having some trouble updating my code and keeping all my existing customizations.
I use Mui exclusively for the DatePicker and DateRangePicker which are the best available for a React app IMO.

My goal is to integrate these components into our UI component library and customize a bit their colors and aspects using Tailwind.
Maybe I didn't put enough time into it but I find it really difficult to achieve this goal.

Here's my current component code using v5

FieldDate.tsx

import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
import { DatePicker as MuiDatePicker } from '@mui/x-date-pickers/DatePicker';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';

import { MuiTextFieldProps } from '@mui/x-date-pickers/internals/components/PureDateInput';
import { formatDateToISO8601ForBackend, isValidDate } from '@teamout/utils';
import React, { HTMLAttributes, useEffect, useState } from 'react';
import { FieldWrapper } from './FieldWrapper';

export const DateInput: React.FC<
  {
    muiProps: MuiTextFieldProps;
    error?: string;
  } & HTMLAttributes<HTMLInputElement>
> = ({ muiProps, error, ...props }) => {
  const { inputRef, inputProps, InputProps } = muiProps;
  return (
    <div className="flex flex-col gap-2 w-full">
      <div className="flex items-center h-10 px-5 w-full rounded-input focus-within:ring-2 focus-within:ring-inset focus-within:ring-primary ring-1 ring-inset ring-gray-300 shadow-sm">
        <input
          ref={inputRef}
          {...inputProps}
          className="px-0 h-8 block w-full text-gray-900 placeholder:text-gray-400 border-0 focus:ring-0 focus:outline-none sm:leading-6 disabled:cursor-not-allowed disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500"
          {...props}
        />
        {InputProps?.endAdornment}
      </div>
      {error && <p className="text-sm text-red-600">{error}</p>}
    </div>
  );
};

export const FieldDate: React.FC<{
  name: string;
  label?: string;
  minDate?: Date;
  maxDate?: Date;
  error?: string;
  defaultValue?: Date;
  required?: boolean;
  className?: string;
}> = ({
  name,
  label,
  minDate,
  maxDate,
  error,
  defaultValue,
  required,
  className,
}) => {
  const [innerValue, setInnerValue] = useState<Date | null>(
    defaultValue ?? null,
  );

  useEffect(() => {
    setInnerValue(defaultValue ?? null);
  }, [defaultValue]);

  return (
    <FieldWrapper
      label={label}
      error={error}
      required={required}
      className={className}
    >
      <LocalizationProvider dateAdapter={AdapterDateFns}>
        <MuiDatePicker
          value={innerValue}
          onChange={(newValue) => setInnerValue(newValue)}
          minDate={minDate}
          maxDate={maxDate}
          renderInput={(props) => (
            <DateInput muiProps={props} data-e2e="date-picker" />
          )}
        />
      </LocalizationProvider>
      <input
        type="hidden"
        name={name}
        value={
          isValidDate(innerValue)
            ? formatDateToISO8601ForBackend(innerValue)
            : ''
        }
      />
    </FieldWrapper>
  );
};

DateRangeField.tsx

import { ArrowRightIcon } from '@heroicons/react/24/solid';
import {
  DateRange,
  DateRangePicker as MuiDateRangePicker,
} from '@mui/x-date-pickers-pro/DateRangePicker';
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { formatDateToISO8601ForBackend, isValidDate } from '@teamout/utils';
import { useEffect, useState } from 'react';
import { FieldWrapper } from '../../atom/form/FieldWrapper';
import { DateInput } from './FieldDate';

export const DateRangePicker: React.FC<{
  value: DateRange<Date>;
  onChange: (value: DateRange<Date>) => void;
  onAccept?: (value: DateRange<Date>) => void;
  minDate?: Date;
  maxDate?: Date;
  setIsOpen?: (isOpen: boolean) => void;
  fromError?: string;
  toError?: string;
}> = ({
  value,
  onChange,
  onAccept,
  minDate,
  maxDate,
  setIsOpen,
  fromError,
  toError,
}) => {
  return (
    <LocalizationProvider dateAdapter={AdapterDateFns}>
      <MuiDateRangePicker
        desktopModeMediaQuery="@media (min-width: 720px)" // https://github.com/cypress-io/cypress/issues/970
        value={value}
        onChange={onChange}
        onAccept={onAccept}
        minDate={minDate}
        maxDate={maxDate}
        className="w-full"
        onOpen={() => setIsOpen && setIsOpen(true)}
        onClose={() => setIsOpen && setIsOpen(false)}
        renderInput={(startProps, endProps) => (
          <div className="flex gap-2 items-center w-full">
            <DateInput
              muiProps={startProps}
              data-e2e="date-range-from"
              error={fromError}
            />
            <ArrowRightIcon className="w-10 h-5" />
            <DateInput
              muiProps={endProps}
              data-e2e="date-range-to"
              error={toError}
            />
          </div>
        )}
      />
    </LocalizationProvider>
  );
};

export const DateRangeField: React.FC<{
  name: string;
  label?: string;
  minDate?: Date;
  maxDate?: Date;
  error?: string;
  fromError?: string;
  toError?: string;
  defaultValue?: DateRange<Date>;
  setIsOpen?: (isOpen: boolean) => void;
  value?: DateRange<Date>;
  onChange?: (value: DateRange<Date>) => void;
  required?: boolean;
  className?: string;
}> = ({
  name,
  label,
  minDate,
  maxDate,
  error,
  fromError,
  toError,
  defaultValue,
  setIsOpen,
  value,
  onChange,
  required,
  className,
}) => {
  const [innerValue, setInnerValue] = useState<DateRange<Date>>(
    defaultValue ?? [null, null],
  );

  useEffect(() => {
    onChange && onChange(innerValue);
  }, [innerValue, onChange]);

  return (
    <FieldWrapper
      label={label}
      error={error}
      required={required}
      className={className}
    >
      <DateRangePicker
        value={value ?? innerValue}
        onChange={(newValue) => setInnerValue(newValue)}
        minDate={minDate}
        maxDate={maxDate}
        setIsOpen={setIsOpen}
        fromError={fromError}
        toError={toError}
      />
      <input
        type="hidden"
        name={`${name}[from]`}
        value={
          isValidDate(innerValue[0])
            ? formatDateToISO8601ForBackend(innerValue[0])
            : ''
        }
      />
      <input
        type="hidden"
        name={`${name}[to]`}
        value={
          isValidDate(innerValue[1])
            ? formatDateToISO8601ForBackend(innerValue[1])
            : ''
        }
      />
    </FieldWrapper>
  );
};

I also use a theme to customize the colors inside the calendar

import { createTheme } from '@mui/material/styles';
import { dateRangePickerDayClasses } from '@mui/x-date-pickers-pro/DateRangePickerDay';
import type {} from '@mui/x-date-pickers-pro/themeAugmentation';
import { pickersDayClasses } from '@mui/x-date-pickers/PickersDay';
import { colors } from './theme';

// History: https://github.com/mui/mui-x/issues/6699
export const muiTheme = createTheme({
  components: {
    MuiPickersDay: {
      styleOverrides: {
        root: {
          [`&.${dateRangePickerDayClasses.day}.${pickersDayClasses.selected}`]:
            {
              backgroundColor: colors.primary,
              '&:hover, &:focus': {
                backgroundColor: colors.primary,
              },
            },
          [`&.${pickersDayClasses.root}.${pickersDayClasses.selected}`]: {
            backgroundColor: colors.primary,
            '&:hover, &:focus': {
              backgroundColor: colors.primary,
            },
          },
        },
      },
    },
    MuiDateRangePickerDay: {
      styleOverrides: {
        rangeIntervalDayHighlight: {
          backgroundColor: colors['primary-light'],
        },
      },
    },
    MuiDialog: {
      styleOverrides: {
        root: {
          '.MuiButton-root': {
            color: colors.primary,
          },
        },
      },
    },
  },
});

Since I use remix.run, I followed the example on this repo to integrate Mui & emotion with Remix: https://github.com/mui/material-ui/tree/master/examples/material-ui-remix-ts

(I'm just not using <CssBaseline /> since we don't need it)

By the way I'm running into another problem with Emotion SSR since on some page load we can see some kind of SVG sprite blinking into the screen then disappear.

I tried to follow the v6 docs and use the slots props of the components but I'm not making good progress so far. I don't know if you could help or guide me through this but it would be very appreciated.

Thanks a lot!

By the way you can find an example of how we use the date range picker on this page https://flights.teamout.com/

Your environment

`npx @mui/envinfo`
System:
    OS: macOS 12.7.1
  Binaries:
    Node: 20.10.0 - ~/.nvm/versions/node/v20.10.0/bin/node
    npm: 10.2.3 - ~/.nvm/versions/node/v20.10.0/bin/npm
    pnpm: 7.14.1 - ~/Library/pnpm/pnpm
  Browsers:
    Chrome: 121.0.6167.160
    Edge: Not Found
    Safari: 15.6.1
  npmPackages:
    @emotion/react: 11.11.1 => 11.11.1 
    @emotion/styled: 11.11.0 => 11.11.0 
    @mui/base:  5.0.0-beta.36 
    @mui/core-downloads-tracker:  5.15.10 
    @mui/material: 5.15.10 => 5.15.10 
    @mui/private-theming:  5.15.9 
    @mui/styled-engine:  5.15.9 
    @mui/system:  5.15.9 
    @mui/types:  7.2.13 
    @mui/utils:  5.15.9 
    @mui/x-date-pickers:  6.19.4 
    @mui/x-date-pickers-pro: 6.19.4 => 6.19.4 
    @mui/x-license-pro:  6.10.2 
    @types/react: 18.2.48 => 18.2.48 
    react: 18.2.0 => 18.2.0 
    react-dom: 18.2.0 => 18.2.0 
    typescript: 5.2.2 => 5.2.2

Mostly using Chrome

Search keywords: date range picker migrate
Order ID: 68974

@lildesert lildesert added status: waiting for maintainer These issues haven't been looked at yet by a maintainer support: commercial Support request from paid users labels Feb 14, 2024
@zannager zannager added support: question Community support but can be turned into an improvement component: pickers This is the name of the generic UI component, not the React module! support: pro standard Support request from a Pro standard plan user. https://mui.com/legal/technical-support-sla/ labels Feb 14, 2024
@lildesert
Copy link
Author

Quick update here, I got an almost working version of my DateField component by updating the code like this

import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
import {
  UseDateFieldProps,
  unstable_useDateField as useDateField,
} from '@mui/x-date-pickers/DateField';
import { DatePicker, DatePickerProps } from '@mui/x-date-pickers/DatePicker';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import {
  BaseSingleInputFieldProps,
  DateValidationError,
  FieldSection,
} from '@mui/x-date-pickers/models';
import { formatDateToISO8601ForBackend, isValidDate } from '@teamout/utils';
import * as React from 'react';
import { FieldWrapper } from './FieldWrapper';

interface DateInputProps
  extends UseDateFieldProps<Date>,
    BaseSingleInputFieldProps<
      Date | null,
      Date,
      FieldSection,
      DateValidationError
    > {
  error?: string;
}

const DateInput = React.forwardRef(
  (props: DateInputProps, ref: React.Ref<HTMLDivElement>) => {
    const { inputRef: externalInputRef, id, error, ...textFieldProps } = props;

    const { ref: inputRef, ...fieldProps } = useDateField<
      Date,
      typeof textFieldProps
    >({
      props: textFieldProps,
      inputRef: externalInputRef,
    });

    return (
      <div className="flex flex-col gap-2 w-full" ref={ref} id={id}>
        <div className="flex items-center h-10 px-5 w-full rounded-input focus-within:ring-2 focus-within:ring-inset focus-within:ring-primary ring-1 ring-inset ring-gray-300 shadow-sm">
          <input
            ref={fieldProps.InputProps?.ref}
            id={id}
            {...fieldProps}
            className="px-0 h-8 block w-full text-gray-900 placeholder:text-gray-400 border-0 focus:ring-0 focus:outline-none sm:leading-6 disabled:cursor-not-allowed disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500"
          />
          {fieldProps.InputProps?.endAdornment}
        </div>
        {error && <p className="text-sm text-red-600">{error}</p>}
      </div>
    );
  },
);

export const FieldDate = React.forwardRef(
  (
    props: {
      name: string;
      label?: string;
      error?: string;
      defaultValue?: Date;
      required?: boolean;
      className?: string;
    } & DatePickerProps<Date>,
    ref: React.Ref<HTMLDivElement>,
  ) => {
    const { name, label, error, defaultValue, required, className, ...rest } =
      props;
    const [innerValue, setInnerValue] = React.useState<Date | null>(
      defaultValue ?? null,
    );

    React.useEffect(() => {
      setInnerValue(defaultValue ?? null);
    }, [defaultValue]);

    return (
      <FieldWrapper
        label={label}
        error={error}
        required={required}
        className={className}
      >
        <LocalizationProvider dateAdapter={AdapterDateFns}>
          <DatePicker
            value={innerValue}
            onChange={(newValue) => setInnerValue(newValue)}
            ref={ref}
            {...rest}
            slots={{ field: DateInput, ...props.slots }}
          />
        </LocalizationProvider>
        <input
          type="hidden"
          name={name}
          value={
            isValidDate(innerValue)
              ? formatDateToISO8601ForBackend(innerValue)
              : ''
          }
        />
      </FieldWrapper>
    );
  },
);

I still got a bunch of errors and warnings because I'm spreading {...fieldProps} inside an input but I guess this is the kind of path I'm expected to follow.

@michelengelen
Copy link
Member

Hi @lildesert,

There is a section in the docs about how to replace the default Field with a browser field: custom field

From your code example it looks like the ref is not transported correctly and you are trying to assign the textFieldProps as props in the useDateField hook. You can just pass the whole object to the function:

const DateInput = React.forwardRef((props: DateInputProps, ref: React.Ref<HTMLDivElement>) => {
  const { id, error, ...textFieldProps } = props;

  const { inputRef, ...fieldProps } = useDateField<Date, typeof textFieldProps>(textFieldProps);

  return (
    <div className="flex flex-col gap-2 w-full" ref={ref} id={id}>
      <div className="flex items-center h-10 px-5 w-full rounded-input focus-within:ring-2 focus-within:ring-inset focus-within:ring-primary ring-1 ring-inset ring-gray-300 shadow-sm">
        <input
          ref={inputRef}
          id={id}
          {...fieldProps}
          className="px-0 h-8 block w-full text-gray-900 placeholder:text-gray-400 border-0 focus:ring-0 focus:outline-none sm:leading-6 disabled:cursor-not-allowed disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500"
        />
        {fieldProps.InputProps?.endAdornment}
      </div>
      {error && <p className="text-sm text-red-600">{error}</p>}
    </div>
  );
});

Did that help you?

PS: For the future please provide a live example in codesandbox or stackblitz. Its way easier for us to help you then! 🙇🏼

@michelengelen michelengelen added status: waiting for author Issue with insufficient information and removed status: waiting for maintainer These issues haven't been looked at yet by a maintainer labels Feb 16, 2024
@michelengelen michelengelen changed the title [question] How to migrate custom DateRangePicker from v5 to v6? [pickers] How to migrate custom DateRangePicker from v5 to v6? Feb 16, 2024
@lildesert
Copy link
Author

Hi @michelengelen, thanks for your help!
Yesterday I was finally able to get some good results with the help of this doc: https://mui.com/x/react-date-pickers/custom-field/

My FieldDate now looks like this

import { unstable_useForkRef as useForkRef } from '@mui/utils';
import {
  BaseSingleInputFieldProps,
  DateValidationError,
  FieldSection,
} from '@mui/x-date-pickers-pro';
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
import {
  UseDateFieldProps,
  unstable_useDateField as useDateField,
} from '@mui/x-date-pickers/DateField';
import {
  DateFieldSlotsComponent,
  DateFieldSlotsComponentsProps,
} from '@mui/x-date-pickers/DateField/DateField.types';
import { DatePicker, DatePickerProps } from '@mui/x-date-pickers/DatePicker';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { useClearableField } from '@mui/x-date-pickers/hooks';
import { formatDateToISO8601ForBackend, isValidDate } from '@teamout/utils';
import * as React from 'react';
import { Input } from './Field';
import { FieldWrapper } from './FieldWrapper';

interface BrowserFieldProps
  extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'> {
  label?: React.ReactNode;
  inputRef?: React.Ref<any>;
  InputProps?: {
    ref?: React.Ref<unknown>;
    endAdornment?: React.ReactNode;
    startAdornment?: React.ReactNode;
  };
  error?: boolean;
  errorMessage?: string;
  focused?: boolean;
  ownerState?: unknown;
  sx?: unknown;
}

type BrowserFieldComponent = ((
  props: BrowserFieldProps & React.RefAttributes<HTMLDivElement>,
) => React.JSX.Element) & { propTypes?: unknown };

export const DateInput = React.forwardRef(
  (props: BrowserFieldProps, ref: React.Ref<HTMLDivElement>) => {
    const {
      disabled,
      id,
      label,
      inputRef,
      InputProps: { ref: containerRef, startAdornment, endAdornment } = {},
      // extracting `error`, 'focused', and `ownerState` as `input` does not support those props
      error,
      errorMessage,
      focused,
      ownerState,
      sx,
      className,
      ...other
    } = props;

    const handleRef = useForkRef(containerRef, ref);

    return (
      <FieldWrapper ref={handleRef} id={id} error={errorMessage}>
        {startAdornment && (
          <div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-5">
            {startAdornment}
          </div>
        )}
        <Input
          withBorder
          withStartAdornment={Boolean(startAdornment)}
          hasError={error}
          ref={inputRef}
          className={className}
          {...other}
        />
        {endAdornment && (
          <div className="absolute inset-y-0 right-0 flex items-center pr-3">
            {endAdornment}
          </div>
        )}
      </FieldWrapper>
    );
  },
) as BrowserFieldComponent;

interface BrowserDateFieldProps
  extends UseDateFieldProps<Date>,
    BaseSingleInputFieldProps<
      Date | null,
      Date,
      FieldSection,
      DateValidationError
    > {}

const BrowserDateField = React.forwardRef(
  (props: BrowserDateFieldProps, ref: React.Ref<HTMLDivElement>) => {
    const {
      inputRef: externalInputRef,
      slots,
      slotProps,
      ...textFieldProps
    } = props;

    const {
      onClear,
      clearable,
      ref: inputRef,
      placeholder,
      ...fieldProps
    } = useDateField<Date, typeof textFieldProps>({
      props: textFieldProps,
      inputRef: externalInputRef,
    });

    /* If you don't need a clear button, you can skip the use of this hook */
    const { InputProps: ProcessedInputProps, fieldProps: processedFieldProps } =
      useClearableField<
        NonNullable<unknown>,
        typeof textFieldProps.InputProps,
        DateFieldSlotsComponent,
        DateFieldSlotsComponentsProps<Date>
      >({
        onClear,
        clearable,
        fieldProps,
        InputProps: fieldProps.InputProps,
        slots,
        slotProps,
      });
    return (
      <DateInput
        ref={ref}
        inputRef={inputRef}
        placeholder="mm/dd/yyyy"
        {...processedFieldProps}
        InputProps={ProcessedInputProps}
      />
    );
  },
);

const BrowserDatePicker = React.forwardRef(
  (props: DatePickerProps<Date>, ref: React.Ref<HTMLDivElement>) => {
    return (
      <DatePicker
        ref={ref}
        {...props}
        slots={{ field: BrowserDateField, ...props.slots }}
      />
    );
  },
);

export const FieldDate: React.FC<
  {
    name: string;
    label?: string;
    error?: string;
    required?: boolean;
    className?: string;
  } & DatePickerProps<Date>
> = ({ name, label, error, required, className, defaultValue, ...rest }) => {
  const [innerValue, setInnerValue] = React.useState<Date | null>(
    defaultValue ?? null,
  );

  React.useEffect(() => {
    setInnerValue(defaultValue ?? null);
  }, [defaultValue]);

  return (
    <FieldWrapper
      label={label}
      error={error}
      required={required}
      className={className}
    >
      <LocalizationProvider dateAdapter={AdapterDateFns}>
        <BrowserDatePicker
          value={innerValue}
          onChange={(newValue) => setInnerValue(newValue)}
          slotProps={{
            field: { clearable: true },
          }}
          {...rest}
        />
      </LocalizationProvider>
      <input
        type="hidden"
        name={name}
        value={
          isValidDate(innerValue)
            ? formatDateToISO8601ForBackend(innerValue)
            : ''
        }
      />
    </FieldWrapper>
  );
};

Sorry for not providing a codesandbox but the Remix / Mui / Tailwind setup could be a bit difficult.

I still have an issue with the SSR of Mui into Remix but I'll probably open another issue if I don't find a solution.

@github-actions github-actions bot removed the status: waiting for author Issue with insufficient information label Feb 16, 2024
Copy link

⚠️ This issue has been closed.
If you have a similar problem, please open a new issue and provide details about your specific problem.
If you can provide additional information related to this topic that could help future readers, please feel free to leave a comment.

How did we do @lildesert?
Your experience with our support team matters to us. If you have a moment, please share your thoughts through our brief survey.

@LukasTy
Copy link
Member

LukasTy commented Feb 16, 2024

Hello @lildesert, we are happy that you are using and enjoying the Pickers package. 🙌
Kudos for taking the time to migrate. 👏

In regards to your question, let's first start with a suggestion - consider wrapping your whole application in a LocalizationProvider instead of creating one for each Picker component usage.

I've tried playing around a bit and the following seemed like the least intrusive overriding changing only what is relevant:

import Box from '@mui/material/Box';
import { TextFieldProps } from '@mui/material/TextField';
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
import { DatePicker as MuiDatePicker } from '@mui/x-date-pickers/DatePicker';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
// import { formatDateToISO8601ForBackend, isValidDate } from '@teamout/utils';
import React, { HTMLAttributes, useEffect, useState } from 'react';
// import { FieldWrapper } from './FieldWrapper';

export const DateInput: React.FC<
  TextFieldProps & {
    error?: string;
  }
> = ({ error, ...other }) => {
  const { inputRef, inputProps, InputProps, size, ownerState, focused, sx, ...rest } = other;
  return (
    <Box
      className="flex flex-col gap-2 w-full"
      data-e2e="date-picker"
      ref={InputProps?.ref}
      // consume `sx` to benefit from the clearable button hiding behavior
      sx={sx}
    >
      <div className="flex items-center h-10 px-5 w-full rounded-input focus-within:ring-2 focus-within:ring-inset focus-within:ring-primary ring-1 ring-inset ring-gray-300 shadow-sm">
        <input
          ref={inputRef}
          {...inputProps}
          className="px-0 h-8 block w-full text-gray-900 placeholder:text-gray-400 border-0 focus:ring-0 focus:outline-none sm:leading-6 disabled:cursor-not-allowed disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500"
          {...rest}
        />
        {InputProps?.endAdornment}
      </div>
      {error && <p className="text-sm text-red-600">{error}</p>}
    </Box>
  );
};

export const FieldDate: React.FC<{
  name: string;
  label?: string;
  minDate?: Date;
  maxDate?: Date;
  error?: string;
  defaultValue?: Date;
  required?: boolean;
  className?: string;
}> = ({ name, label, minDate, maxDate, error, defaultValue, required, className }) => {
  const [innerValue, setInnerValue] = useState<Date | null>(defaultValue ?? null);

  useEffect(() => {
    setInnerValue(defaultValue ?? null);
  }, [defaultValue]);

  return (
    <div
      // label={label}
      // error={error}
      // required={required}
      className={className}
    >
      <LocalizationProvider dateAdapter={AdapterDateFns}>
        <MuiDatePicker
          value={innerValue}
          onChange={(newValue) => setInnerValue(newValue)}
          minDate={minDate}
          maxDate={maxDate}
          slotProps={{ field: { clearable: true } }}
          slots={{ textField: DateInput }}
        />
      </LocalizationProvider>
      <input
        type="hidden"
        name={name}
        value={innerValue ? innerValue.toISOString() : ''}
        // value={isValidDate(innerValue) ? formatDateToISO8601ForBackend(innerValue) : ''}
      />
    </div>
  );
};

Things to note:

  • it seems to be enough to only override the textField slot in your case
  • note the usage of a Box component consuming the provided sx prop, which applies relevant styles to keep the clearable button hiding logic. If you don't care for it, feel free to just exclude sx from the props applied to the input element
  • the DateInput in the above example is missing ref propagation. If you don't need to reference these Picker components in your code base - it shouldn't be a problem, but consider adding ref forwarding.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: pickers This is the name of the generic UI component, not the React module! support: commercial Support request from paid users support: pro standard Support request from a Pro standard plan user. https://mui.com/legal/technical-support-sla/ support: question Community support but can be turned into an improvement
Projects
None yet
Development

No branches or pull requests

4 participants