mui / mui-x

MUI X: Build complex and data-rich applications using a growing list of advanced React components, like the Data Grid, Date and Time Pickers, Charts, and more!
https://mui.com/x/
4.51k stars 1.31k forks source link

[pickers][DatePicker] DatePicker with different locale also changes the input value #14580

Open brunomgurgel opened 1 month ago

brunomgurgel commented 1 month ago

Steps to reproduce

Link to live example: https://codesandbox.io/s/mgtfx8?file=/src/Demo.tsx

Steps:

  1. Add a locale with a different date format than en-US

Current behavior

Currently the input value changes to the provided date format.

Expected behavior

I understand the current behavior and I see advantages on it sometimes. But I wish there was a possibility to just change the UI and the value always remains on the same format. Which makes it easier to parse the date on an API that knows nothing about the language and is always expecting on the en-US format.

Context

I want the UI to show the date in the format of the provided locale but I want the value to be always in one format (in my case en-US). Specially using server actions in Next.js we don't have controlled inputs, which means that what is sent to the server is the input value itself.

image

Your environment

No response

Search keywords: Date Picker, Localization

Search keywords:

michelengelen commented 1 month ago

Hey @brunomgurgel I don't get what you are trying to achieve. Is it a standardized format in which you send and receive the value without affecting the UI?

You can do that very simply by using the value

import * as React from "react";
import dayjs from "dayjs";
import "dayjs/locale/de";
import "dayjs/locale/en-gb";
import "dayjs/locale/zh-cn";
import Stack from "@mui/material/Stack";
import ToggleButton from "@mui/material/ToggleButton";
import ToggleButtonGroup from "@mui/material/ToggleButtonGroup";
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
import { DateField } from "@mui/x-date-pickers/DateField";
import { TimeField } from "@mui/x-date-pickers/TimeField";
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";

const locales = ["en", "en-gb", "zh-cn", "de"];

type LocaleKey = (typeof locales)[number];

export default function LocalizationDayjs() {
  const [locale, setLocale] = React.useState<LocaleKey>("en");
  const [date, setDate] = React.useState(dayjs("2022-04-17"));

  console.log("date", date.toISOString());

  return (
    <LocalizationProvider dateAdapter={AdapterDayjs} adapterLocale={locale}>
      <Stack spacing={3} sx={{ width: 300 }}>
        <ToggleButtonGroup
          value={locale}
          exclusive
          fullWidth
          onChange={(event, newLocale) => {
            if (newLocale != null) {
              setLocale(newLocale);
            }
          }}
        >
          {locales.map((localeItem) => (
            <ToggleButton key={localeItem} value={localeItem}>
              {localeItem}
            </ToggleButton>
          ))}
        </ToggleButtonGroup>
        <DateField label="Date" value={date} />
        <TimeField label="Time" value={date} />
      </Stack>
    </LocalizationProvider>
  );
}

In CSB: DEMO

toISOString() will convert the current value to a machine-readable standardized string that can be used to store to and receive from a backend.

brunomgurgel commented 1 month ago

Hey @michelengelen , thank you for the fast response!

I'm trying to achieve something similar to what you're saying, but there are some differences.

I'm using Server Actions, which means that I don't send the form with an onSubmit on the form component. Instead it uses the native HTML form submission, just getting the value from the date picker.

Which means that if my language is de for example, it will send a date as 11/09/2024, but when the API (that knows nothing about locale) parses this date, it interprets as 9 of november, instead of 11 of september.

What I want to know is if I can change the value to be on an ISO format or on a standardized format (in my case the en-US format).

Below is how my input value gets rendered when using de as the locale adapter

image

michelengelen commented 1 month ago

This is a bit tricky. Could you provide us with a minimal reproduction repository we can execute and play around with?

I would honestly love to test it and possibly add a section about this in the docs if possible!

brunomgurgel commented 1 month ago

I created a minimal repro in my personal github

When you submit the form you will see on the server logs and on the screen the resulting value, and if you change the language you will see the value changes.

I added the workaround I'm currently doing, but I don't feel is the best option.

Thank you again for the support!

flaviendelangle commented 1 month ago

@brunomgurgel I'm interested to discuss a bit with you about server actions and how they are used in the Date and Time Pickers.

We currently have 2 DOM structure, the old one with a <input /> and the new one that uses several contentEditable spans (for accessibility reason). In the next major, the new structure will become the default one and we will deprecate the old one. To be compatible with server actions, we kept a hidden <input /> DOM element that for now contains the formatted date (just like the old structure have it). Would your problem be solved if the value stored in the hidden <input /> was a fixed format (probably the ISO one) instead of the formatted value?

You would then have:

You can find the doc about the new structure here and play with it by adding enableAccessibleFieldDOMStructure to any picker of field component.

brunomgurgel commented 1 month ago

That would work great! And those are exciting news. Thank you a lot! I will play around with this new structure

michelengelen commented 1 month ago

side note: useFormState got deprecated and replaced with a new hook useActionState

michelengelen commented 1 month ago

Another side note: even in the official useActionState documentation they use hidden inputs as helpers for the formState, so IMHO something like this:

<DatePicker
  label="Start Date"
  value={formState.startDate}
  onChange={(date) => setFormState({ ...formState, startDate: date })}
  name="startDate"
  slotProps={{
    textField: {
      required: true,
    },
  }}
/>

<input
  type="hidden"
  name="startDate"
  value={formState.startDate?.toISOString()}
/>

is a perfectly viable workaround for now.

@flaviendelangle Should we keep this open as a feature request to apply the ISO value to the hidden field?

brunomgurgel commented 1 month ago

Yep, I agree! For now we will doing this workaround and I will keep an eye on this update

michelengelen commented 1 month ago

Yep, I agree! For now we will doing this workaround and I will keep an eye on this update

Great to see that we have a solution, even if it is just a workaround for now. I did add this to the board so that the team can discuss this in the grooming session.

Thanks for your patience and cooperation on this @brunomgurgel 🙇🏼

brunomgurgel commented 1 month ago

Thank you guys for all the help and fast response! I love your product and you're doing an awesome job!

flaviendelangle commented 1 month ago

@flaviendelangle Should we keep this open as a feature request to apply the ISO value to the hidden field?

I would create a dedicated issue clearly focused around server actions