diff --git a/CLAUDE.md b/CLAUDE.md
index cad5725ce..a2b4f2521 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -155,6 +155,18 @@ The repo uses Husky with lint-staged. On commit:
- **Formatting**: Prettier 3.4.2
- **CSS**: Sass 1.93.2
+## Bug Fix Workflow (TDD)
+
+When fixing bugs, always follow Test-Driven Development:
+
+1. **Write the test first** - Create a failing test that reproduces the bug
+2. **Confirm it fails** - Run the test to verify it captures the broken behavior
+3. **Implement the fix** - Make the minimal code change to fix the issue
+4. **Verify the test passes** - Run the test again to confirm the fix works
+5. **Run full test suite** - Ensure no regressions with `yarn test:ci`
+
+This ensures every bug fix has regression coverage and documents the expected behavior.
+
## Code Conventions
- **Prettier handles all code formatting** - don't worry about tabs vs spaces
diff --git a/src/test/timepicker_test.test.tsx b/src/test/timepicker_test.test.tsx
index 2d677dd22..ab9fd96aa 100644
--- a/src/test/timepicker_test.test.tsx
+++ b/src/test/timepicker_test.test.tsx
@@ -80,6 +80,44 @@ describe("TimePicker", () => {
expect(resizeObserverCallback).toBe(null);
});
});
+
+ it("should not cause infinite loop when resize callback is called multiple times", async () => {
+ const { container } = render(
+ ,
+ );
+
+ await waitFor(() => {
+ expect(mockObserve).toHaveBeenCalledTimes(1);
+ });
+
+ const resizeObserverCallback = getResizeObserverCallback();
+ const mockObserveElement = mockObserve.mock.calls[0][0];
+ expect(typeof resizeObserverCallback).toBe("function");
+
+ const timeList = container.querySelector(
+ ".react-datepicker__time-list",
+ ) as HTMLElement;
+ expect(timeList).not.toBeNull();
+
+ // Get initial height
+ const initialHeight = timeList.style.height;
+
+ // Call resize callback multiple times (simulating what would happen in an infinite loop)
+ if (resizeObserverCallback) {
+ resizeObserverCallback([], mockObserveElement);
+ resizeObserverCallback([], mockObserveElement);
+ resizeObserverCallback([], mockObserveElement);
+ }
+
+ // Height should remain stable (not grow infinitely)
+ // The fix ensures setState is only called when height actually changes
+ expect(timeList.style.height).toBe(initialHeight);
+ });
});
it("should update on input time change", () => {
diff --git a/src/time.tsx b/src/time.tsx
index ed0c50f76..aed28c994 100644
--- a/src/time.tsx
+++ b/src/time.tsx
@@ -97,9 +97,14 @@ export default class Time extends Component {
private updateContainerHeight(): void {
if (this.props.monthRef && this.header) {
- this.setState({
- height: this.props.monthRef.clientHeight - this.header.clientHeight,
- });
+ const newHeight =
+ this.props.monthRef.clientHeight - this.header.clientHeight;
+ // Only update state if height actually changed to prevent infinite resize loops
+ if (this.state.height !== newHeight) {
+ this.setState({
+ height: newHeight,
+ });
+ }
}
}