Skip to content

Commit 6503446

Browse files
authored
fix(useDateFieldState): fix setValue to work when field is empty (#8417)
* fix useDateFieldState to commit values when setValue called on empty field * cleanup to avoid conflict * dedupe test function * remove dupe condition
1 parent bcb1eb5 commit 6503446

File tree

3 files changed

+88
-1
lines changed

3 files changed

+88
-1
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2025 Adobe. All rights reserved.
3+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License. You may obtain a copy
5+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under
8+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
* OF ANY KIND, either express or implied. See the License for the specific language
10+
* governing permissions and limitations under the License.
11+
*/
12+
13+
import {createCalendar, parseDate} from '@internationalized/date';
14+
import {Meta, StoryObj} from '@storybook/react';
15+
import React, {ReactElement, useRef} from 'react';
16+
import {useDateField} from '../src';
17+
import {useDateFieldState} from '@react-stately/datepicker';
18+
import {useLocale} from '@react-aria/i18n';
19+
20+
export function ProgrammaticSetValueExampleRender(): ReactElement {
21+
let {locale} = useLocale();
22+
let state = useDateFieldState({locale, createCalendar});
23+
let ref = useRef<HTMLDivElement>(null);
24+
let {fieldProps} = useDateField({'aria-label': 'Date'}, state, ref);
25+
return (
26+
<div>
27+
<div {...fieldProps} ref={ref} data-testid="field">
28+
{state.segments.map((seg, i) => <span key={i}>{seg.text}</span>)}
29+
</div>
30+
<button onClick={() => state.setValue(parseDate('2020-01-01'))} data-testid="set">Set</button>
31+
</div>
32+
);
33+
}
34+
35+
export default {
36+
title: 'Date and Time/useDatePicker',
37+
excludeStories: ['ProgrammaticSetValueExampleRender']
38+
} as Meta<typeof ProgrammaticSetValueExampleRender>;
39+
40+
type Story = StoryObj<typeof ProgrammaticSetValueExampleRender>;
41+
42+
export const ProgrammaticSetValueExample: Story = {
43+
render: () => <ProgrammaticSetValueExampleRender />
44+
};
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2025 Adobe. All rights reserved.
3+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License. You may obtain a copy
5+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under
8+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
* OF ANY KIND, either express or implied. See the License for the specific language
10+
* governing permissions and limitations under the License.
11+
*/
12+
13+
import {pointerMap, render} from '@react-spectrum/test-utils-internal';
14+
import {ProgrammaticSetValueExampleRender} from '../stories/useDatePicker.stories';
15+
import React, {} from 'react';
16+
import userEvent from '@testing-library/user-event';
17+
18+
describe('useDatePicker', function () {
19+
let user;
20+
beforeAll(() => {
21+
user = userEvent.setup({delay: null, pointerMap});
22+
jest.useFakeTimers();
23+
});
24+
25+
it('should commit programmatically setValue when field is empty', async () => {
26+
let {getByTestId} = render(<ProgrammaticSetValueExampleRender />);
27+
28+
expect(getByTestId('field')).toHaveTextContent('mm/dd/yyyy');
29+
await user.click(getByTestId('set'));
30+
expect(getByTestId('field')).toHaveTextContent('2020');
31+
});
32+
});

packages/@react-stately/datepicker/src/useDateFieldState.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,18 @@ export function useDateFieldState<T extends DateValue = DateValue>(props: DateFi
260260
setDate(null);
261261
setPlaceholderDate(createPlaceholderDate(props.placeholderValue, granularity, calendar, defaultTimeZone));
262262
setValidSegments({});
263-
} else if (validKeys.length >= allKeys.length || (validKeys.length === allKeys.length - 1 && allSegments.dayPeriod && !validSegments.dayPeriod && clearedSegment.current !== 'dayPeriod')) {
263+
} else if (
264+
validKeys.length === 0 ||
265+
validKeys.length >= allKeys.length ||
266+
(validKeys.length === allKeys.length - 1 && allSegments.dayPeriod && !validSegments.dayPeriod && clearedSegment.current !== 'dayPeriod')
267+
) {
268+
// If the field was empty (no valid segments) or all segments are completed, commit the new value.
269+
// When committing from an empty state, mark every segment as valid so value is committed.
270+
if (validKeys.length === 0) {
271+
validSegments = {...allSegments};
272+
setValidSegments(validSegments);
273+
}
274+
264275
// The display calendar should not have any effect on the emitted value.
265276
// Emit dates in the same calendar as the original value, if any, otherwise gregorian.
266277
newValue = toCalendar(newValue, v?.calendar || new GregorianCalendar());

0 commit comments

Comments
 (0)