Skip to content

Commit 7a885db

Browse files
committed
fix(input): avoid restoring stale selection after rerender
1 parent 535e12a commit 7a885db

2 files changed

Lines changed: 40 additions & 5 deletions

File tree

src/hooks/useCountExceed.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,18 @@ export default function useCountExceed<
1616
const [selection, setSelection] = React.useState<
1717
[start: number, end: number] | null
1818
>(null);
19+
const getTargetRef = React.useRef(getTarget);
20+
21+
React.useEffect(() => {
22+
getTargetRef.current = getTarget;
23+
}, [getTarget]);
1924

2025
React.useEffect(() => {
2126
if (selection) {
22-
getTarget()?.setSelectionRange(...selection);
27+
getTargetRef.current()?.setSelectionRange(...selection);
28+
setSelection(null);
2329
}
24-
}, [getTarget, selection]);
30+
}, [selection]);
2531

2632
const getExceedValue = React.useCallback(
2733
(currentValue: string, isComposing: boolean) => {
@@ -39,15 +45,15 @@ export default function useCountExceed<
3945

4046
if (currentValue !== nextValue) {
4147
setSelection([
42-
getTarget()?.selectionStart || 0,
43-
getTarget()?.selectionEnd || 0,
48+
getTargetRef.current()?.selectionStart || 0,
49+
getTargetRef.current()?.selectionEnd || 0,
4450
]);
4551
}
4652
}
4753

4854
return nextValue;
4955
},
50-
[countConfig, getTarget],
56+
[countConfig],
5157
);
5258

5359
return getExceedValue;

tests/count.test.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,35 @@ describe('Input.Count', () => {
109109
expect(setSelectionRange).toHaveBeenCalledWith(2, 2);
110110
});
111111

112+
it('should not restore selection again on unrelated rerender', () => {
113+
const count = {
114+
show: true,
115+
max: 3,
116+
exceedFormatter: (val: string, { max }: { max: number }) =>
117+
val.slice(0, max),
118+
};
119+
const { container, rerender } = render(
120+
<Input count={count} defaultValue="123" />,
121+
);
122+
123+
const input = container.querySelector('input')!;
124+
const setSelectionRange = jest.spyOn(input, 'setSelectionRange');
125+
126+
fireEvent.change(input, {
127+
target: {
128+
selectionStart: 2,
129+
selectionEnd: 2,
130+
value: '1a23',
131+
},
132+
});
133+
134+
expect(setSelectionRange).toHaveBeenCalledTimes(1);
135+
136+
rerender(<Input count={count} defaultValue="123" className="rerender" />);
137+
138+
expect(setSelectionRange).toHaveBeenCalledTimes(1);
139+
});
140+
112141
it('input using the input method should trigger onChange once', () => {
113142
const onChange = jest.fn();
114143
const { container } = render(<Input onChange={onChange} />);

0 commit comments

Comments
 (0)