From 3aba0073a2e047e242d7f1a44c32d5bdf710a3be Mon Sep 17 00:00:00 2001 From: Amumu Date: Thu, 28 Dec 2023 14:32:14 +0800 Subject: [PATCH] fix: trigger onChange twice when inputting using the input method (#61) * fix: trigger onChange twice when inputting using the input method * chore: code style --- src/Input.tsx | 21 +++++++++++++++------ src/interface.ts | 4 ++++ tests/count.test.tsx | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 6 deletions(-) diff --git a/src/Input.tsx b/src/Input.tsx index abce2a9..866b211 100644 --- a/src/Input.tsx +++ b/src/Input.tsx @@ -10,7 +10,7 @@ import React, { } from 'react'; import BaseInput from './BaseInput'; import useCount from './hooks/useCount'; -import type { InputProps, InputRef } from './interface'; +import type { ChangeEventInfo, InputProps, InputRef } from './interface'; import type { InputFocusOptions } from './utils/commonUtils'; import { resolveOnChange, triggerFocus } from './utils/commonUtils'; @@ -40,7 +40,7 @@ const Input = forwardRef((props, ref) => { } = props; const [focused, setFocused] = useState(false); - const compositionRef = React.useRef(false); + const compositionRef = useRef(false); const inputRef = useRef(null); @@ -58,7 +58,7 @@ const Input = forwardRef((props, ref) => { value === undefined || value === null ? '' : String(value); // =================== Select Range =================== - const [selection, setSelection] = React.useState< + const [selection, setSelection] = useState< [start: number, end: number] | null >(null); @@ -97,6 +97,7 @@ const Input = forwardRef((props, ref) => { | React.ChangeEvent | React.CompositionEvent, currentValue: string, + info: ChangeEventInfo, ) => { let cutValue = currentValue; @@ -116,6 +117,10 @@ const Input = forwardRef((props, ref) => { inputRef.current?.selectionEnd || 0, ]); } + } else if (info.source === 'compositionEnd') { + // Avoid triggering twice + // https://github.com/ant-design/ant-design/issues/46587 + return; } setValue(cutValue); @@ -124,21 +129,25 @@ const Input = forwardRef((props, ref) => { } }; - React.useEffect(() => { + useEffect(() => { if (selection) { inputRef.current?.setSelectionRange(...selection); } }, [selection]); const onInternalChange: React.ChangeEventHandler = (e) => { - triggerChange(e, e.target.value); + triggerChange(e, e.target.value, { + source: 'change', + }); }; const onInternalCompositionEnd = ( e: React.CompositionEvent, ) => { compositionRef.current = false; - triggerChange(e, e.currentTarget.value); + triggerChange(e, e.currentTarget.value, { + source: 'compositionEnd', + }); onCompositionEnd?.(e); }; diff --git a/src/interface.ts b/src/interface.ts index 0270361..1800125 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -149,3 +149,7 @@ export interface InputRef { select: () => void; input: HTMLInputElement | null; } + +export interface ChangeEventInfo { + source: 'compositionEnd' | 'change'; +} diff --git a/tests/count.test.tsx b/tests/count.test.tsx index a6c22e4..ccee16e 100644 --- a/tests/count.test.tsx +++ b/tests/count.test.tsx @@ -109,6 +109,45 @@ describe('Input.Count', () => { expect(setSelectionRange).toHaveBeenCalledWith(2, 2); }); + it('input using the input method should trigger onChange once', () => { + const onChange = jest.fn(); + const { container } = render(); + const input = container.querySelector('input')!; + fireEvent.compositionStart(input); + fireEvent.compositionUpdate(input, { data: '你' }); + fireEvent.compositionEnd(input, { data: '你好' }); + fireEvent.input(input, { target: { value: '你好' } }); + expect(onChange).toHaveBeenCalledTimes(1); + }); + + it('using the input method to enter the cropped content should trigger onChange twice', () => { + const onChange = jest.fn(); + const onCompositionEnd = jest.fn(); + const { container } = render( + + getSegments(val) + .filter((seg) => seg.index + seg.segment.length <= max) + .map((seg) => seg.segment) + .join(''), + }} + onChange={onChange} + onCompositionEnd={onCompositionEnd} + />, + ); + const input = container.querySelector('input')!; + fireEvent.compositionStart(input); + fireEvent.compositionUpdate(input, { target: { value: '你' } }); + fireEvent.compositionUpdate(input, { target: { value: '你好' } }); + fireEvent.compositionUpdate(input, { target: { value: '你好世' } }); + fireEvent.compositionUpdate(input, { target: { value: '你好世界' } }); + fireEvent.compositionEnd(input, { target: { value: '你好世界' } }); + expect(input?.value).toEqual('你好世'); + }); + describe('cls', () => { it('raw', () => { const { container } = render(