Material-ui-pickers: Picking a date from calendar should not modify the TextInput until the user clicks OK

Created on 20 May 2019  路  24Comments  路  Source: mui-org/material-ui-pickers

The issue is clearly demonstrated from the demo: https://material-ui-pickers.dev/demo/datepicker

Open a calendar -> start clicking on dates -> you will see the underlying TextInput change.

bug 馃悰

All 24 comments

Hi @dmtrKovalenko

I can still see the issue happening with v3.0.0. I will create a sandbox now.

Here is a reproduction of the problem: https://codesandbox.io/embed/nifty-bash-ji3yl?fontsize=14

Right now it works as expected.
You can see that only other text fields are changed. That鈥檚 because they are relying actually on the state of current picker. But the picker鈥檚 textfield do not changing.
So if you are need to display accepted date use onAccept instead

I don't understand how it works as expected... imagine you have a query (from - to) to which on every onChange on from-to you fetch some data... the way the KeyboardDatePicker works now, whenever the user opens the calendar and starts clicking on dates (without accepting a date) it triggers the onChange event which you must handle to reflect the changes in both the Calendar and the TextInput.

You said it works now... care to share a code example of KeyboardDatePicker that is not changing the underlying TextInput?

Thanks

Use onAccept prop to make network requests

OK, what about validation? :) That's inherently tied to the input's value and triggered onChange. IMO there needs to be a separate state for the calendar date and the field date.

Actually no, we had tons of problems with different states. You must run the validation on the current state.

I ran into this too. It's a breaking change and was announced as such. But please let me explain why it was a step in the wrong direction.

In pure HTML, <input type="text"> only fires onchange when the field loses focus and has changed. React controlled components (and also material-ui components) in contrast fire their onChange whenever the user types, even before the field loses focus. And the DatePicker now behaves similarly, onAccept would be its equivalent of onBlur, and it even conveniently gives you the date value.
Note: If you set autoOk={true}, this behavior doesn't matter for mouse input, as every onChange implies exactly one onAccept.

For the a <DatePicker variant="inline"/>, this behavior might be debatable, as the user still sees the remainder of the form. Yet it's inconsistent with other material-ui components that open a Popover, specifically the Select component (demo). With mouse input, it behaves similar to autoOk -- every click closes the popover and propagates the value everywhere. With keyboard input, going up and down through the list (arrow keys) does not propagate the value anywhere outside the component. That is, the component is a controlled component, and it still has some state of its own (the focus). If you try the same thing with the DatePicker, i.e. open the popover/dialog but then use arrow keys to select, you're actually triggering the onChange event. This is inconsistent with Select, we just don't normally observe it because there is no <Select autoOk={false}> and users use the mouse.

For the regular <DatePicker autoOk={false}/> with dialog, I'd argue that this behavior is quite confusing. The dialog and its backdrop/overlay obscure the view of the rest of the form. Thus any state changes triggered to there can confuse the user when they half-see them happening in the background, especially if validation errors on other fields are appearing and that causes layout changes/jumps. While a dialog is open, the background is not supposed to change. This can currently be observed in the official demo by opening the "Basic example" dialog and clicking a future date, which makes the other two fields invalid immediately.

Another indication that this behavior is surprising rather than a usual controlled component is that the "Cancel" button (onDismiss) had to be implemented with a useRef -- a bit like history-keeping. An undesirable consequence is that opening a DatePicker with an empty or invalid value and then clicking "Cancel" will not result in the same state as before, but instead the field will now have the closest valid value assigned. This is not what a "Cancel" button should do.

In conclusion, I think onChange should fire exactly when the user decided for that value and can't go back anymore (aside from some proper "undo" function offered on a higher application level, or changing the value manually again). And that's when they click "OK" (or when "OK" is auto-clicked for them). For inline-pickers, it's also the time when the picker closes. Even if the picker is a controlled component, the currently selected date should be internal state, just like with the Select. This "fire when user decided and can't trivially abort anymore" requirement is consistent with text boxes, selects, and all other form inputs I can currently think of, and it should make validation behave as the user expects.

Workaround: Ditch the "Cancel" and "OK" buttons (is there a way to hide them?), use autoOk everywhere. There's not really a point for a user to select a date if their intention isn't to click "OK" immediately anyway. Unfortunately, autoOk isn't such a nice solution for a DateTimePicker.

To reproduce the "Cancel fills form" behavior: https://codesandbox.io/s/gallant-hill-f3blh

  • Open dialog by clicking the calendar icon.
  • Click "Cancel".
  • Input box and "Selected date" should remain empty, but get set to current date.

Another workaround is to create a new component that takes care of keeping the state, which should be equivalent to material-ui's old behavior.

import {DatePicker as MuiDatePicker} from '@material-ui/pickers';
export default class StatefulDatePicker extends React.PureComponent {
    state = {
        date: null,
    };
    render() {
        const {value, onChange, onAccept, ...restProps} = this.props;
        return <MuiDatePicker
            {...restProps}
            value={this.state.date || value}
            onChange={date => this.setState({date})}
            onAccept={date => {
                if (onChange) onChange(date);
                if (onAccept) onAccept(date);
            }}
            onClose={() => {
                this.setState({date: null});
                if (onClose) onClose();
            }}/>;
    }
}

The above doesn't work for me because after onClose() is called, there's an extra onChange() call happening.

This is the workaround that works for me:

import React from "react";
import PropTypes from "prop-types";
import { DatePicker } from "@material-ui/pickers";

class ModalDatePicker extends React.Component {
  static propTypes = {
    label: PropTypes.string.isRequired,
    value: PropTypes.instanceOf(Date),
    onChange: PropTypes.func.isRequired
  };

  open = false;
  state = {
    date: null
  };

  render() {
    const { label, value, onChange } = this.props;
    return (
      <DatePicker
        label={label}
        format="d MMMM yyy"
        disableFuture
        clearable
        value={this.open ? this.state.date : value}
        onChange={date => this.setState({ date: date })}
        onAccept={date => onChange(date)}
        onOpen={() => (this.open = true)}
        onClose={() => (this.open = false)}
      />
    );
  }
}

also having this issue

I am using material-ui date picker and using useState() to set the value on change of date. I am using value prop of the KeyBoardDatePicker Component to set the updated date. But the datepicker dialog does not update the date on the dialog. Can anyone suggest what the issue might be?

How was this resolved? The variant inline has no _Ok_ equivalent as dialog does. The onAccept is not triggered when you are done entering the date into the field. I don't want to make calls based off of onChange and I see it's being said to use onAccept but it's not being triggered when user uses the keyboard as opposed to the dialog. Can someone please explain.

@nalzayat which version are you using?

@nalzayat I was able to get the datepicker working with onAccept , here's my implementation for your reference.
<MuiPickersUtilsProvider utils={DateFnsUtils}> <KeyboardDatePicker autoOk required className={classes.datePicker} variant="inline" margin="normal" id="date-picker-dialog" label="Select Financial Date" format="MM/dd/yyyy" value={selectedDate} onChange={setSelectedDate} onAccept={date => handleDateChange(date)} KeyboardButtonProps={{ "aria-label": "change date" }} /> </MuiPickersUtilsProvider>

@oliviertassinari I am using "@material-ui/pickers": "3.2.9", . As for @asad-shaikh-ivp , I copied your segment and the problem persists. Essentially, when you use the inline feature, the onAccept callback is not triggered because the date is never actually 'accepted'. As it is when you press the 'Ok' or when you select a date in the actual calendar. @callmeberzerker commented with the reproduction of the problem above.

If you can reproduce it with v4.0.0-alpha.8, please open a new issue :).

@nalzayat I used the attribute autoOkso that the date is selected as user clicks on it since there is no OK button in the inline variant.

@asad-shaikh-ivp It works for the calendar regardless of the autoOk. But what I'm referring to is when you physically type the date, it doesn't trigger the onAccept. I will see if it was fixed in the v4.0.0-alpha.8 now. :)

Ok just had a moment to pick this back up. I tried this scenario using v4.0.0-alpha.8. I can confirm @asad-shaikh-ivp that you are correct, the autoOk does in fact trigger the callback. However, this only applies if you are selecting the date in the calendar. This does not work for if you enter in the date manually. Are there plans to make this possible..?

Yes you can use the OnInputevent in that case similarly how onAcceptis written. It will capture the date entered by the user using the editor instead of the selector.

Thank you guys!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

basselAhmed picture basselAhmed  路  3Comments

killjoy2013 picture killjoy2013  路  3Comments

katy6514 picture katy6514  路  3Comments

danmce picture danmce  路  3Comments

Lysander picture Lysander  路  3Comments