Skip to content

Commit 502c588

Browse files
fix: disable SuggestionList during text composition (#2205)
### 🎯 Goal Disables `SuggestionList` component when text composition (with Korean and Japanese keyboard inputs) is active to prevent text duplication on submission.  During text composition: ![image](https://github.com/GetStream/stream-chat-react/assets/43254280/a50bf257-7bce-4b7d-ac5b-fb15d1b024ce) After text composition: ![image](https://github.com/GetStream/stream-chat-react/assets/43254280/da3f2042-d0c9-4670-af10-3b280f6882a8)
1 parent ebde52e commit 502c588

File tree

2 files changed

+57
-27
lines changed

2 files changed

+57
-27
lines changed

src/components/AutoCompleteTextarea/Textarea.jsx

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export class ReactTextareaAutocomplete extends React.Component {
4949
currentTrigger: null,
5050
data: null,
5151
dataLoading: false,
52+
isComposing: false,
5253
left: null,
5354
selectionEnd: 0,
5455
selectionStart: 0,
@@ -664,37 +665,39 @@ export class ReactTextareaAutocomplete extends React.Component {
664665
SuggestionList = DefaultSuggestionList,
665666
} = this.props;
666667

668+
const { isComposing } = this.state;
669+
667670
const triggerProps = this.getTriggerProps();
668671

669672
if (
670-
triggerProps.values &&
671-
triggerProps.currentTrigger &&
672-
!(disableMentions && triggerProps.currentTrigger === '@')
673-
) {
674-
return (
675-
<div
676-
className={clsx(
677-
'rta__autocomplete',
678-
'str-chat__suggestion-list-container',
679-
dropdownClassName,
680-
)}
681-
ref={this.setDropdownRef}
682-
style={dropdownStyle}
683-
>
684-
<SuggestionList
685-
className={clsx('str-chat__suggestion-list', listClassName)}
686-
dropdownScroll={this._dropdownScroll}
687-
itemClassName={clsx('str-chat__suggestion-list-item', itemClassName)}
688-
itemStyle={itemStyle}
689-
onSelect={this._onSelect}
690-
SuggestionItem={SuggestionItem}
691-
{...triggerProps}
692-
/>
693-
</div>
694-
);
695-
}
673+
isComposing ||
674+
!triggerProps.values ||
675+
!triggerProps.currentTrigger ||
676+
(disableMentions && triggerProps.currentTrigger === '@')
677+
)
678+
return null;
696679

697-
return null;
680+
return (
681+
<div
682+
className={clsx(
683+
'rta__autocomplete',
684+
'str-chat__suggestion-list-container',
685+
dropdownClassName,
686+
)}
687+
ref={this.setDropdownRef}
688+
style={dropdownStyle}
689+
>
690+
<SuggestionList
691+
className={clsx('str-chat__suggestion-list', listClassName)}
692+
dropdownScroll={this._dropdownScroll}
693+
itemClassName={clsx('str-chat__suggestion-list-item', itemClassName)}
694+
itemStyle={itemStyle}
695+
onSelect={this._onSelect}
696+
SuggestionItem={SuggestionItem}
697+
{...triggerProps}
698+
/>
699+
</div>
700+
);
698701
}
699702

700703
render() {
@@ -745,6 +748,8 @@ export class ReactTextareaAutocomplete extends React.Component {
745748
this._onClickAndBlurHandler(e);
746749
onClick?.(e);
747750
}}
751+
onCompositionEnd={() => this.setState((pv) => ({ ...pv, isComposing: false }))}
752+
onCompositionStart={() => this.setState((pv) => ({ ...pv, isComposing: true }))}
748753
onFocus={(e) => {
749754
this.props.onFocus?.(e);
750755
onFocus?.(e);

src/components/ChatAutoComplete/__tests__/ChatAutocomplete.test.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,31 @@ describe('ChatAutoComplete', () => {
202202
expect(userText).toHaveLength(0);
203203
});
204204

205+
it('should disable popup list when the input is in "isComposing" state', async () => {
206+
const { findAllByText, findByTestId, queryAllByText, typeText } = await renderComponent();
207+
208+
const messageInput = await findByTestId('message-input');
209+
210+
act(() => {
211+
const cStartEvent = new Event('compositionstart', { bubbles: true });
212+
messageInput.dispatchEvent(cStartEvent);
213+
});
214+
215+
const userAutocompleteText = `@${user.name}`;
216+
typeText(userAutocompleteText);
217+
218+
// eslint-disable-next-line jest-dom/prefer-in-document
219+
expect(await queryAllByText(user.name)).toHaveLength(0);
220+
221+
act(() => {
222+
const cEndEvent = new Event('compositionend', { bubbles: true });
223+
messageInput.dispatchEvent(cEndEvent);
224+
});
225+
226+
// eslint-disable-next-line jest-dom/prefer-in-document
227+
expect(await findAllByText(user.name)).toHaveLength(2);
228+
});
229+
205230
it('should use the queryMembers API for mentions if a channel has many members', async () => {
206231
const users = Array(100).fill().map(generateUser);
207232
const members = users.map((u) => generateMember({ user: u }));

0 commit comments

Comments
 (0)