Repository: mui/material-ui (TypeScript)
Date: 2022-01-25 05:34:05+00:00
Task type: Bug Fix

Issue:
MobileDatePicker's Cancel button does not work.
### Duplicates

- [X] I have searched the existing issues

### Latest version

- [X] I have tested the latest version

### Current behavior 😯

1. Changes are not canceled when the CANCEL button is pressed.
2. Selecting a date will fire onChange, and the date will be updated.

### Expected behavior 🤔

Make the firing timing of onChange only when the OK button is pressed.

### Steps to reproduce 🕹

Steps:

1. Access to https://codesandbox.io/s/eloquent-feather-iznb0?file=/src/index.tsx
2. Press the TextField(For mobile)
3. Select an appropriate date.( You can see the date change at this time.)
4. Press the CANCEL button.(You can confirm that the changes have not been canceled. )

### Context 🔦

_No response_

### Your environment 🌎

<details>
  <summary>`npx @mui/envinfo`</summary>

```
  Don't forget to mention which browser you used.

    Chrome: 97.0.4692.71
    Firefox: 96.0.1

  Output from `npx @mui/envinfo` goes here.

  System:
    OS: macOS 11.6.1
    CPU: (8) x64 Apple M1
    Memory: 33.54 MB / 16.00 GB
    Shell: 5.8 - /bin/zsh
  Binaries:
    Node: 16.13.1 - ~/.anyenv/envs/nodenv/versions/16.13.1/bin/node
    Yarn: 1.22.17 - /opt/homebrew/bin/yarn
    npm: 8.1.2 - ~/.anyenv/envs/nodenv/versions/16.13.1/bin/npm
  Managers:
    Homebrew: 3.3.9 - /opt/homebrew/bin/brew
    pip3: 21.0.1 - /opt/homebrew/bin/pip3
    RubyGems: 3.0.3 - /usr/bin/gem
  Utilities:
    Make: 3.81 - /usr/bin/make
    GCC: 4.2.1 - /usr/bin/gcc
    Git: 2.30.1 - /opt/homebrew/bin/git
    Clang: 12.0.5 - /usr/bin/clang
    FFmpeg: 4.3.2 - /opt/homebrew/bin/ffmpeg
  Servers:
    Apache: 2.4.48 - /usr/sbin/apachectl
  Virtualization:
    Docker: 20.10.7 - /usr/local/bin/docker
  IDEs:
    Nano: 2.0.6 - /usr/bin/nano
    Vim: 8.2 - /usr/bin/vim
    Xcode: /undefined - /usr/bin/xcodebuild
  Languages:
    Bash: 3.2.57 - /bin/bash
    Perl: 5.30.2 - /usr/bin/perl
    PHP: 7.3.29 - /usr/bin/php
    Python: 2.7.16 - /usr/bin/python
    Python3: 3.9.2 - /opt/homebrew/bin/python3
    Ruby: 2.6.3 - /usr/bin/ruby
  Databases:
    SQLite: 3.32.3 - /usr/bin/sqlite3
  Browsers:
    Chrome: 97.0.4692.71
    Firefox: 96.0.1
    Safari: 15.1
```

</details>



Issue discussion:
has anyone started working on this issue? Can I pick this one?
  
Not sure if this helps, but until this gets fixed, you could try creating a "cancel" effect on your end.
You could use onOpen to determine what the value was before changes, and use onAccept and onClose to determine if the OK button was clicked or if the Cancel button was clicked then maintain values accordingly.

Solution patch:
diff --git a/packages/mui-lab/src/internal/pickers/hooks/usePickerState.ts b/packages/mui-lab/src/internal/pickers/hooks/usePickerState.ts
--- a/packages/mui-lab/src/internal/pickers/hooks/usePickerState.ts
+++ b/packages/mui-lab/src/internal/pickers/hooks/usePickerState.ts
@@ -1,7 +1,7 @@
 import * as React from 'react';
-import { useOpenState } from './useOpenState';
 import { WrapperVariant } from '../wrappers/WrapperVariantContext';
-import { useUtils, MuiPickersAdapter } from './useUtils';
+import { useOpenState } from './useOpenState';
+import { MuiPickersAdapter, useUtils } from './useUtils';
 
 export interface PickerStateValueManager<TInputValue, TDateValue> {
   areValuesEqual: (
@@ -70,6 +70,8 @@ export function usePickerState<TInput, TDateValue>(
     dispatch({ type: 'reset', payload: parsedDateValue });
   }
 
+  const [initialDate, setInitialDate] = React.useState<TDateValue>(draftState.committed);
+
   // Mobile keyboard view is a special case.
   // When it's open picker should work like closed, cause we are just showing text field
   const [isMobileKeyboardViewOpen, setMobileKeyboardViewOpen] = React.useState(false);
@@ -80,7 +82,7 @@ export function usePickerState<TInput, TDateValue>(
 
       if (needClosePicker) {
         setIsOpen(false);
-
+        setInitialDate(acceptedDate);
         if (onAccept) {
           onAccept(acceptedDate);
         }
@@ -94,7 +96,7 @@ export function usePickerState<TInput, TDateValue>(
       open: isOpen,
       onClear: () => acceptDate(valueManager.emptyValue, true),
       onAccept: () => acceptDate(draftState.draft, true),
-      onDismiss: () => setIsOpen(false),
+      onDismiss: () => acceptDate(initialDate, true),
       onSetToday: () => {
         const now = utils.date() as TDateValue;
         dispatch({ type: 'update', payload: now });
@@ -107,8 +109,8 @@ export function usePickerState<TInput, TDateValue>(
       isOpen,
       utils,
       draftState.draft,
-      setIsOpen,
       valueManager.emptyValue,
+      initialDate,
     ],
   );
 


Test patch:
diff --git a/packages/mui-lab/src/MobileDatePicker/MobileDatePicker.test.tsx b/packages/mui-lab/src/MobileDatePicker/MobileDatePicker.test.tsx
--- a/packages/mui-lab/src/MobileDatePicker/MobileDatePicker.test.tsx
+++ b/packages/mui-lab/src/MobileDatePicker/MobileDatePicker.test.tsx
@@ -310,4 +310,32 @@ describe('<MobileDatePicker />', () => {
       expect(handleOpen.callCount).to.equal(0);
     });
   });
+
+  it('should retain the values on clicking Cancel button', () => {
+    const onChangeCallback = spy();
+
+    const initialDateValue = new Date('Jan 26, 2022');
+
+    render(
+      <MobileDatePicker
+        value={initialDateValue}
+        onChange={onChangeCallback}
+        renderInput={(params) => <TextField {...params} />}
+      />,
+    );
+
+    fireEvent.click(screen.getByRole('textbox'));
+    fireEvent.click(screen.getByLabelText('Jan 31, 2022')); // changing date followed by clicking cancel button
+
+    fireEvent.click(screen.getByText(/cancel/i));
+
+    /**
+     * picking second arg, as callback is being called twice.
+     * First, while selecting temporary date (Jan 31, 2022 in this case)
+     * Second, while clicking cancel (which is setting date back to initial value)
+     */
+    const finalDateValue = new Date(onChangeCallback.args[1][0]);
+
+    expect(finalDateValue.getTime()).to.equal(initialDateValue.getTime());
+  });
 });