Repository: mui/material-ui (TypeScript)
Date: 2021-06-30 15:23:50+00:00
Task type: Bug Fix

Issue:
[Timepicker] With null value, after selecting hour default time is current minute of now rather than a more logical default like 00
- [x] The issue is present in the latest release.
- [x] I have searched the [issues](https://github.com/mui-org/material-ui/issues) of this repository and believe that this is not a duplicate.

## Current Behavior 😯

1. Pass null to timepicker.
2. Select hour (defaults to nothing)
3. Select minute, defaults to whatever minute right now is, which is a strange default, especially considering hour is null to start.

## Expected Behavior 🤔

1. Pass null to timepicker.
2. Select hour (defaults to nothing)
3. Select time, defaults to nothing or 00 (0 may be required because I understand changing hour sets a value, I think this new value should not have current time).

## Context

When someone adds a new time to an object that doesn't have it, they are unlikely to have minutes defaulting to now.  They are usually setting a value that is in the future and likely to be on an hour, or half hour, or maybe 15 minutes.  But 0 is a more logical default than now.


Issue discussion:

Solution patch:
diff --git a/packages/material-ui-lab/src/ClockPicker/ClockPicker.tsx b/packages/material-ui-lab/src/ClockPicker/ClockPicker.tsx
--- a/packages/material-ui-lab/src/ClockPicker/ClockPicker.tsx
+++ b/packages/material-ui-lab/src/ClockPicker/ClockPicker.tsx
@@ -215,9 +215,14 @@ function ClockPicker<TDate>(inProps: ClockPickerProps<TDate>) {
 
   const now = useNow<TDate>();
   const utils = useUtils<TDate>();
-  const dateOrNow = date || now;
+  const midnight = utils.setSeconds(utils.setMinutes(utils.setHours(now, 0), 0), 0);
+  const dateOrMidnight = date || midnight;
 
-  const { meridiemMode, handleMeridiemChange } = useMeridiemMode<TDate>(dateOrNow, ampm, onChange);
+  const { meridiemMode, handleMeridiemChange } = useMeridiemMode<TDate>(
+    dateOrMidnight,
+    ampm,
+    onChange,
+  );
 
   const isTimeDisabled = React.useCallback(
     (rawValue: number, viewType: ClockView) => {
@@ -284,12 +289,12 @@ function ClockPicker<TDate>(inProps: ClockPickerProps<TDate>) {
       case 'hours': {
         const handleHoursChange = (value: number, isFinish?: PickerSelectionState) => {
           const valueWithMeridiem = convertValueToMeridiem(value, meridiemMode, ampm);
-          onChange(utils.setHours(dateOrNow, valueWithMeridiem), isFinish);
+          onChange(utils.setHours(dateOrMidnight, valueWithMeridiem), isFinish);
         };
 
         return {
           onChange: handleHoursChange,
-          value: utils.getHours(dateOrNow),
+          value: utils.getHours(dateOrMidnight),
           children: getHourNumbers({
             date,
             utils,
@@ -303,9 +308,9 @@ function ClockPicker<TDate>(inProps: ClockPickerProps<TDate>) {
       }
 
       case 'minutes': {
-        const minutesValue = utils.getMinutes(dateOrNow);
+        const minutesValue = utils.getMinutes(dateOrMidnight);
         const handleMinutesChange = (value: number, isFinish?: PickerSelectionState) => {
-          onChange(utils.setMinutes(dateOrNow, value), isFinish);
+          onChange(utils.setMinutes(dateOrMidnight, value), isFinish);
         };
 
         return {
@@ -323,9 +328,9 @@ function ClockPicker<TDate>(inProps: ClockPickerProps<TDate>) {
       }
 
       case 'seconds': {
-        const secondsValue = utils.getSeconds(dateOrNow);
+        const secondsValue = utils.getSeconds(dateOrMidnight);
         const handleSecondsChange = (value: number, isFinish?: PickerSelectionState) => {
-          onChange(utils.setSeconds(dateOrNow, value), isFinish);
+          onChange(utils.setSeconds(dateOrMidnight, value), isFinish);
         };
 
         return {
@@ -355,7 +360,7 @@ function ClockPicker<TDate>(inProps: ClockPickerProps<TDate>) {
     getSecondsClockNumberText,
     meridiemMode,
     onChange,
-    dateOrNow,
+    dateOrMidnight,
     isTimeDisabled,
     selectedId,
   ]);


Test patch:
diff --git a/packages/material-ui-lab/src/ClockPicker/ClockPicker.test.tsx b/packages/material-ui-lab/src/ClockPicker/ClockPicker.test.tsx
--- a/packages/material-ui-lab/src/ClockPicker/ClockPicker.test.tsx
+++ b/packages/material-ui-lab/src/ClockPicker/ClockPicker.test.tsx
@@ -341,4 +341,40 @@ describe('<ClockPicker />', () => {
       expect(handleChange.callCount).to.equal(0);
     });
   });
+
+  describe('default value', () => {
+    it('if value is provided, keeps minutes and seconds when changing hour', () => {
+      const handleChange = spy();
+      render(
+        <ClockPicker
+          autoFocus
+          date={adapterToUse.date('2019-01-01T04:19:47.000')}
+          onChange={handleChange}
+        />,
+      );
+      const listbox = screen.getByRole('listbox');
+
+      fireEvent.keyDown(listbox, { key: 'ArrowUp' });
+
+      expect(handleChange.callCount).to.equal(1);
+      const [newDate] = handleChange.firstCall.args;
+      expect(adapterToUse.getHours(newDate)).to.equal(5);
+      expect(adapterToUse.getMinutes(newDate)).to.equal(19);
+      expect(adapterToUse.getSeconds(newDate)).to.equal(47);
+    });
+
+    it('if value is not provided, uses zero as default for minutes and seconds when selecting hour', () => {
+      const handleChange = spy();
+      render(<ClockPicker autoFocus date={null} onChange={handleChange} />);
+      const listbox = screen.getByRole('listbox');
+
+      fireEvent.keyDown(listbox, { key: 'ArrowUp' });
+
+      expect(handleChange.callCount).to.equal(1);
+      const [newDate] = handleChange.firstCall.args;
+      expect(adapterToUse.getHours(newDate)).to.equal(1);
+      expect(adapterToUse.getMinutes(newDate)).to.equal(0);
+      expect(adapterToUse.getSeconds(newDate)).to.equal(0);
+    });
+  });
 });