Skip to content

Commit 379e8d6

Browse files
igorman007snowystingerLFDanLu
authored
fix: controlled tooltips are not close properly (#6418)
Co-authored-by: Robert Snow <[email protected]> Co-authored-by: Daniel Lu <[email protected]>
1 parent fb8f2c2 commit 379e8d6

File tree

2 files changed

+66
-3
lines changed

2 files changed

+66
-3
lines changed

packages/@react-stately/tooltip/src/useTooltipTriggerState.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export function useTooltipTriggerState(props: TooltipTriggerProps = {}): Tooltip
4646
let {isOpen, open, close} = useOverlayTriggerState(props);
4747
let id = useMemo(() => `${++tooltipId}`, []);
4848
let closeTimeout = useRef<ReturnType<typeof setTimeout>>();
49+
let closeCallback = useRef<() => void>(close);
4950

5051
let ensureTooltipEntry = () => {
5152
tooltips[id] = hideTooltip;
@@ -81,11 +82,11 @@ export function useTooltipTriggerState(props: TooltipTriggerProps = {}): Tooltip
8182
if (immediate || closeDelay <= 0) {
8283
clearTimeout(closeTimeout.current);
8384
closeTimeout.current = null;
84-
close();
85+
closeCallback.current();
8586
} else if (!closeTimeout.current) {
8687
closeTimeout.current = setTimeout(() => {
8788
closeTimeout.current = null;
88-
close();
89+
closeCallback.current();
8990
}, closeDelay);
9091
}
9192

@@ -119,6 +120,10 @@ export function useTooltipTriggerState(props: TooltipTriggerProps = {}): Tooltip
119120
}
120121
};
121122

123+
useEffect(() => {
124+
closeCallback.current = close;
125+
}, [close]);
126+
122127
// eslint-disable-next-line arrow-body-style
123128
useEffect(() => {
124129
return () => {

packages/@react-stately/tooltip/test/useTooltipTriggerState.test.js

+59-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ function TooltipTrigger(props) {
3737

3838
return (
3939
<span>
40-
<button aria-label="trigger" ref={ref} {...triggerProps}>
40+
<button aria-label={props.label ?? 'trigger'} ref={ref} {...triggerProps}>
4141
{props.children}
4242
</button>
4343
{state.isOpen &&
@@ -46,6 +46,23 @@ function TooltipTrigger(props) {
4646
);
4747
}
4848

49+
function ManualTooltipTrigger(props) {
50+
let [isOpen, setOpen] = React.useState(false);
51+
52+
const onOpenChange = (isOpen) => {
53+
props.onOpenChange(isOpen);
54+
setOpen(isOpen);
55+
};
56+
57+
return (
58+
<TooltipTrigger
59+
label={props.label}
60+
onOpenChange={onOpenChange}
61+
isOpen={isOpen}
62+
tooltip={props.tooltip} />
63+
);
64+
}
65+
4966
/**
5067
* Most of the tests for useTooltipTriggerState are in the React Spectrum package at
5168
* @react-spectrum/tooltip/test/TooltipTrigger.test.js. The React Spectrum tooltip
@@ -196,4 +213,45 @@ describe('useTooltipTriggerState', function () {
196213
expect(onOpenChange).toHaveBeenCalledWith(false);
197214
});
198215
});
216+
217+
describe('multiple controlled tooltips', () => {
218+
it('closes previus tooltip when opening a new one', () => {
219+
let secondOnOpenChange = jest.fn();
220+
221+
let {queryByRole, getByLabelText} = render(
222+
<>
223+
<ManualTooltipTrigger onOpenChange={onOpenChange} tooltip="First tooltip" label="trigger1">
224+
Trigger 1
225+
</ManualTooltipTrigger>
226+
227+
<ManualTooltipTrigger onOpenChange={secondOnOpenChange} tooltip="Second tooltip" label="trigger2">
228+
Trigger 2
229+
</ManualTooltipTrigger>
230+
</>
231+
);
232+
233+
fireEvent.mouseDown(document.body);
234+
fireEvent.mouseUp(document.body);
235+
236+
let button1 = getByLabelText('trigger1');
237+
fireEvent.mouseEnter(button1);
238+
fireEvent.mouseMove(button1);
239+
240+
// run through open timer and confirm that it has opened
241+
act(() => jest.advanceTimersByTime(TOOLTIP_DELAY));
242+
243+
expect(onOpenChange).toHaveBeenCalledWith(true);
244+
expect(queryByRole('tooltip')).toBeVisible();
245+
246+
let button2 = getByLabelText('trigger2');
247+
fireEvent.mouseEnter(button2);
248+
fireEvent.mouseMove(button2);
249+
250+
// run through open timer and confirm that it has opened
251+
act(() => jest.advanceTimersByTime(TOOLTIP_DELAY));
252+
253+
expect(onOpenChange).toHaveBeenCalledTimes(2);
254+
expect(onOpenChange).toHaveBeenCalledWith(false);
255+
});
256+
});
199257
});

0 commit comments

Comments
 (0)