Yanick Rochon
Yanick Rochon

Reputation: 53526

Cannot reset MUI DatePicker from keydown event

I came across this weird behavior that I cannot explain. The sandbox for the code can be found here.

I'm trying to add the possibility to reset a readOnly field by pressing DEL or BACKSPACE but it does not work. While trying to reproduce the issue, I can reset it when pressing a button, but not when typing on the keyboard; why?

import React, { useCallback, useState } from "react";
import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField";
import AdapterDayjs from "@mui/lab/AdapterDayjs";
import LocalizationProvider from "@mui/lab/LocalizationProvider";
import DatePicker from "@mui/lab/DatePicker";
import Typography from "@mui/material/Typography";

import frLocaleDate from "dayjs/locale/fr";

export default function BasicDatePicker() {
  const [value, setValue] = useState(null);
  const [message, setMessage] = useState("");

  const handleChanged = useCallback((newValue) => {
    setMessage("Value changed to " + newValue);
    setValue(newValue);
  }, []);
  const handleInputKeyDown = useCallback((event) => {
    if (event.keyCode === 8 || event.keyCode === 46) {
      setMessage("Reset through handleInputKeyDown");
      setValue(null);
    }
  }, []);
  const handleReset = useCallback(() => {
    setMessage("Reset through handleReset");
    setValue(null);
  }, []);

  return (
    <LocalizationProvider dateAdapter={AdapterDayjs} locale={frLocaleDate}>
      <Typography>Try pressing DEL or BACKSPACE to clear</Typography>
      <DatePicker
        clearable
        showTodayButton
        label="Basic example"
        inputFormat="LL"
        value={value}
        onChange={handleChanged}
        renderInput={({ InputProps, inputProps, ...props }) => (
          <TextField
            fullWidth
            margin="dense"
            size="small"
            onKeyDown={handleInputKeyDown}
            InputProps={{ ...InputProps }}
            inputProps={{ ...inputProps, readOnly: true, placeholder: "" }}
            {...props}
          />
        )}
      />
      <Button onClick={handleReset}>Reset</Button>
      <Typography>
        Debug: <pre>{message}</pre>
      </Typography>
      <Typography>
        Current value: <pre>{JSON.stringify(value)}</pre>
      </Typography>
    </LocalizationProvider>
  );
}

I tried to delay the setValue(null) inside of the handleInputKeyDown function, but even if the value is null, the input still displays a value.

** Update **

The problem occurs only when the field has focus. If I change the handleInputKeyDown like this, press DEL and click away from the input field (i.e. causing it to lose focus), then the field is reset after 2 seconds (when the setTimeout calls the handler function).

const handleInputKeyDown = useCallback((event) => {
  if (event.keyCode === 8 || event.keyCode === 46) {
    setTimeout(() => {
      setMessage("Reset through handleInputKeyDown");
      setValue(null);
    }, 2000);
  }
}, []);

I tried adding a ref to the DatePicker component, then calling ref.current.blur();, but it does not work, and the element does not lose focus.

** Update 2 **

Following Emanuele Scarabattoli's answer and explanation, this also seems to work in my particular case :

const handleInputKeyDown = useCallback((event) => {
  //event.preventDefault();
  if (event.keyCode === 8 || event.keyCode === 46) {
    setTimeout(() => {
      event.target.blur();  // blur outside the scope of keyDown
      setMessage("Reset through handleInputKeyDown");
      setValue(null);
    }, 0);
  }
}, []);

Which raises this other issue : When the field is focused, changing the value will not update the display. This is problematic when the user selects the field, but the program updates it programmatically; the values gets updated but the user does not have a feedback of the changes.

Upvotes: 2

Views: 2953

Answers (1)

Emanuele Scarabattoli
Emanuele Scarabattoli

Reputation: 4469

After your suggestion in the update, I got this workaround:

const handleInputKeyDown = useCallback((event) => {
  event.target.blur() // As you suggested
  event.preventDefault() // This does the trick
  if (event.keyCode === 8 || event.keyCode === 46) {
    setMessage("Reset through handleInputKeyDown");
    setValue(null);
  }
}, [setMessage, setValue]); // Better to add, as per React documentation

Possible explanation

Disclaimer: this explanation is not demonstrated and it is not necessarily true!

Since the DEL key, as default behavior, is used as a key to delete a character, my impression is that the default behavior have to be prevented to make it work differently. Probably this is due to something related to event.target.value in the event. I mean, possibly, the browser, in case of DEL or ESC pressed, is propagating an event that, at the end, does something like valueOfTheInput = event.target.value, even if the value itself is not changed, so we need to avoid this.

Upvotes: 3

Related Questions