From bed5eadd21a1b4c91287aa7999aa6eedae34e3d4 Mon Sep 17 00:00:00 2001 From: Rahul kushwaha Date: Tue, 6 Jun 2023 22:41:41 +0530 Subject: [PATCH 01/15] refactor ReportActionItemMessageEdit to functional component --- .../report/ReportActionItemMessageEdit.js | 466 +++++++++--------- 1 file changed, 231 insertions(+), 235 deletions(-) diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index 602425f4c0c..ad349076ba5 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -1,6 +1,6 @@ /* eslint-disable rulesdir/onyx-props-must-have-default */ import lodashGet from 'lodash/get'; -import React from 'react'; +import React, {useState, useRef, useMemo, useEffect, useCallback} from 'react'; // eslint-disable-next-line no-restricted-imports import {InteractionManager, Keyboard, Pressable, TouchableOpacity, View} from 'react-native'; import PropTypes from 'prop-types'; @@ -74,315 +74,311 @@ const defaultProps = { preferredSkinTone: CONST.EMOJI_DEFAULT_SKIN_TONE, }; -class ReportActionItemMessageEdit extends React.Component { - constructor(props) { - super(props); - this.updateDraft = this.updateDraft.bind(this); - this.deleteDraft = this.deleteDraft.bind(this); - this.debouncedSaveDraft = _.debounce(this.debouncedSaveDraft.bind(this), 1000); - this.publishDraft = this.publishDraft.bind(this); - this.triggerSaveOrCancel = this.triggerSaveOrCancel.bind(this); - this.onSelectionChange = this.onSelectionChange.bind(this); - this.addEmojiToTextBox = this.addEmojiToTextBox.bind(this); - this.setExceededMaxCommentLength = this.setExceededMaxCommentLength.bind(this); - this.saveButtonID = 'saveButton'; - this.cancelButtonID = 'cancelButton'; - this.emojiButtonID = 'emojiButton'; - this.messageEditInput = 'messageEditInput'; - - let draftMessage; - if (this.props.draftMessage === this.props.action.message[0].html) { +function ReportActionItemMessageEdit(props) { + const {action, draftMessage, forwardedRef, index, isKeyboardShown, isSmallScreenWidth, preferredSkinTone, reportID, shouldDisableEmojiPicker, translate} = props; + + const [draft, setDraft] = useState(() => { + if (draftMessage === action.message[0].html) { // We only convert the report action message to markdown if the draft message is unchanged. const parser = new ExpensiMark(); - draftMessage = parser.htmlToMarkdown(this.props.draftMessage).trim(); - } else { - // We need to decode saved draft message because it's escaped before saving. - draftMessage = Str.htmlDecode(this.props.draftMessage); + return parser.htmlToMarkdown(draftMessage).trim(); } - - this.state = { - draft: draftMessage, - selection: { - start: 0, - end: 0, - }, - isFocused: false, - hasExceededMaxCommentLength: false, - }; - } - - componentDidMount() { + // We need to decode saved draft message because it's escaped before saving. + return Str.htmlDecode(draftMessage); + }); + const [selection, setSelection] = useState({start: 0, end: 0}); + const [isFocused, setIsFocused] = useState(false); + const [hasExceededMaxCommentLength, setHasExceededMaxCommentLength] = useState(false); + + const textInputRef = useRef(null); + + // native ids + const saveButtonID = 'saveButton'; + const cancelButtonID = 'cancelButton'; + const emojiButtonID = 'emojiButton'; + const messageEditInput = 'messageEditInput'; + + useEffect(() => { // For mobile Safari, updating the selection prop on an unfocused input will cause it to automatically gain focus // and subsequent programmatic focus shifts (e.g., modal focus trap) to show the blue frame (:focus-visible style), // so we need to ensure that it is only updated after focus. - this.setState((prevState) => ({ - selection: { - start: prevState.draft.length, - end: prevState.draft.length, - }, - })); - } - - componentWillUnmount() { - // Skip if this is not the focused message so the other edit composer stays focused. - if (!this.state.isFocused) { - return; - } + setDraft((prevDraft) => { + setSelection({ + start: prevDraft.length, + end: prevDraft.length, + }); + return prevDraft; + }); + }, []); - // Show the main composer when the focused message is deleted from another client - // to prevent the main composer stays hidden until we swtich to another chat. - ComposerActions.setShouldShowComposeInput(true); - } + useEffect( + () => () => { + // Skip if this is not the focused message so the other edit composer stays focused. + if (!isFocused) { + return; + } + + // Show the main composer when the focused message is deleted from another client + // to prevent the main composer stays hidden until we swtich to another chat. + ComposerActions.setShouldShowComposeInput(true); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps -- for willUnmount lifecycle + [], + ); /** * Update Selection on change cursor position. * * @param {Event} e */ - onSelectionChange(e) { - this.setState({selection: e.nativeEvent.selection}); - } + const onSelectionChange = useCallback((e) => { + setSelection(e.nativeEvent.selection); + }, []); /** * Updates the composer when the comment length is exceeded * Shows red borders and prevents the comment from being sent * - * @param {Boolean} hasExceededMaxCommentLength + * @param {Boolean} hasExceeded */ - setExceededMaxCommentLength(hasExceededMaxCommentLength) { - this.setState({hasExceededMaxCommentLength}); - } + const setExceededMaxCommentLength = useCallback((hasExceeded) => { + setHasExceededMaxCommentLength(hasExceeded); + }, []); + + /** + * Save the draft of the comment. This debounced so that we're not ceaselessly saving your edit. Saving the draft + * allows one to navigate somewhere else and come back to the comment and still have it in edit mode. + * @param {String} newDraft + */ + const debouncedSaveDraft = useMemo( + () => + _.debounce((newDraft) => { + Report.saveReportActionDraft(reportID, action.reportActionID, newDraft); + }, 1000), + [reportID, action.reportActionID], + ); /** * Update the value of the draft in Onyx * - * @param {String} draft + * @param {String} newDraftValue */ - updateDraft(draft) { - const {text: newDraft = '', emojis = []} = EmojiUtils.replaceEmojis(draft, this.props.isSmallScreenWidth, this.props.preferredSkinTone); - - if (!_.isEmpty(emojis)) { - User.updateFrequentlyUsedEmojis(EmojiUtils.getFrequentlyUsedEmojis(emojis)); - } + const updateDraft = useCallback( + (newDraftValue) => { + const {text: newDraft = '', emojis = []} = EmojiUtils.replaceEmojis(newDraftValue, isSmallScreenWidth, preferredSkinTone); - this.setState((prevState) => { - const newState = {draft: newDraft}; - if (draft !== newDraft) { - const remainder = prevState.draft.slice(prevState.selection.end).length; - newState.selection = { - start: newDraft.length - remainder, - end: newDraft.length - remainder, - }; + if (!_.isEmpty(emojis)) { + User.updateFrequentlyUsedEmojis(EmojiUtils.getFrequentlyUsedEmojis(emojis)); } - return newState; - }); - - // This component is rendered only when draft is set to a non-empty string. In order to prevent component - // unmount when user deletes content of textarea, we set previous message instead of empty string. - if (newDraft.trim().length > 0) { - // We want to escape the draft message to differentiate the HTML from the report action and the HTML the user drafted. - this.debouncedSaveDraft(_.escape(newDraft)); - } else { - this.debouncedSaveDraft(this.props.action.message[0].html); - } - } + if (newDraftValue !== newDraft) { + setSelection((prevSelection) => { + const remainder = draft.slice(prevSelection.end).length; + return { + start: newDraft.length - remainder, + end: newDraft.length - remainder, + }; + }); + } + setDraft(newDraft); + + // This component is rendered only when draft is set to a non-empty string. In order to prevent component + // unmount when user deletes content of textarea, we set previous message instead of empty string. + if (newDraft.trim().length > 0) { + // We want to escape the draft message to differentiate the HTML from the report action and the HTML the user drafted. + debouncedSaveDraft(_.escape(newDraft)); + } else { + debouncedSaveDraft(action.message[0].html); + } + }, + [action.message, draft, debouncedSaveDraft, isSmallScreenWidth, preferredSkinTone], + ); /** * Delete the draft of the comment being edited. This will take the comment out of "edit mode" with the old content. */ - deleteDraft() { - this.debouncedSaveDraft.cancel(); - Report.saveReportActionDraft(this.props.reportID, this.props.action.reportActionID, ''); + const deleteDraft = useCallback(() => { + debouncedSaveDraft.cancel(); + Report.saveReportActionDraft(reportID, action.reportActionID, ''); ComposerActions.setShouldShowComposeInput(true); ReportActionComposeFocusManager.focus(); // Scroll to the last comment after editing to make sure the whole comment is clearly visible in the report. - if (this.props.index === 0) { + if (index === 0) { const keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', () => { - ReportScrollManager.scrollToIndex({animated: true, index: this.props.index}, false); + ReportScrollManager.scrollToIndex({animated: true, index}, false); keyboardDidHideListener.remove(); }); } - } - - /** - * Save the draft of the comment. This debounced so that we're not ceaselessly saving your edit. Saving the draft - * allows one to navigate somewhere else and come back to the comment and still have it in edit mode. - * @param {String} newDraft - */ - debouncedSaveDraft(newDraft) { - Report.saveReportActionDraft(this.props.reportID, this.props.action.reportActionID, newDraft); - } + }, [action.reportActionID, debouncedSaveDraft, index, reportID]); /** * Save the draft of the comment to be the new comment message. This will take the comment out of "edit mode" with * the new content. */ - publishDraft() { + const publishDraft = useCallback(() => { // Do nothing if draft exceed the character limit - if (ReportUtils.getCommentLength(this.state.draft) > CONST.MAX_COMMENT_LENGTH) { + if (ReportUtils.getCommentLength(draft) > CONST.MAX_COMMENT_LENGTH) { return; } // To prevent re-mount after user saves edit before debounce duration (example: within 1 second), we cancel // debounce here. - this.debouncedSaveDraft.cancel(); + debouncedSaveDraft.cancel(); - const trimmedNewDraft = this.state.draft.trim(); + const trimmedNewDraft = draft.trim(); // When user tries to save the empty message, it will delete it. Prompt the user to confirm deleting. if (!trimmedNewDraft) { - ReportActionContextMenu.showDeleteModal(this.props.reportID, this.props.action, false, this.deleteDraft, () => - InteractionManager.runAfterInteractions(() => this.textInput.focus()), - ); + ReportActionContextMenu.showDeleteModal(reportID, action, false, deleteDraft, () => InteractionManager.runAfterInteractions(() => textInputRef.current.focus())); return; } - Report.editReportComment(this.props.reportID, this.props.action, trimmedNewDraft); - this.deleteDraft(); - } + Report.editReportComment(reportID, action, trimmedNewDraft); + deleteDraft(); + }, [action, debouncedSaveDraft, deleteDraft, draft, reportID]); /** * @param {String} emoji */ - addEmojiToTextBox(emoji) { - this.setState((prevState) => ({ - selection: { - start: prevState.selection.start + emoji.length, - end: prevState.selection.start + emoji.length, - }, - })); - this.updateDraft(ComposerUtils.insertText(this.state.draft, this.state.selection, emoji)); - } + const addEmojiToTextBox = useCallback( + (emoji) => { + setSelection((prevSelection) => ({ + start: prevSelection.start + emoji.length, + end: prevSelection.start + emoji.length, + })); + updateDraft(ComposerUtils.insertText(draft, selection, emoji)); + }, + [draft, selection, updateDraft], + ); /** * Key event handlers that short cut to saving/canceling. * * @param {Event} e */ - triggerSaveOrCancel(e) { - if (!e || ComposerUtils.canSkipTriggerHotkeys(this.props.isSmallScreenWidth, this.props.isKeyboardShown)) { - return; - } - if (e.key === CONST.KEYBOARD_SHORTCUTS.ENTER.shortcutKey && !e.shiftKey) { - e.preventDefault(); - this.publishDraft(); - } else if (e.key === CONST.KEYBOARD_SHORTCUTS.ESCAPE.shortcutKey) { - e.preventDefault(); - this.deleteDraft(); - } - } - - render() { - const hasExceededMaxCommentLength = this.state.hasExceededMaxCommentLength; - return ( - <> - - - - [styles.chatItemSubmitButton, StyleUtils.getButtonBackgroundColorStyle(getButtonState(hovered, pressed))]} - nativeID={this.cancelButtonID} - onPress={this.deleteDraft} + const triggerSaveOrCancel = useCallback( + (e) => { + if (!e || ComposerUtils.canSkipTriggerHotkeys(isSmallScreenWidth, isKeyboardShown)) { + return; + } + if (e.key === CONST.KEYBOARD_SHORTCUTS.ENTER.shortcutKey && !e.shiftKey) { + e.preventDefault(); + publishDraft(); + } else if (e.key === CONST.KEYBOARD_SHORTCUTS.ESCAPE.shortcutKey) { + e.preventDefault(); + deleteDraft(); + } + }, + [deleteDraft, isKeyboardShown, isSmallScreenWidth, publishDraft], + ); + + return ( + <> + + + + [styles.chatItemSubmitButton, StyleUtils.getButtonBackgroundColorStyle(getButtonState(hovered, pressed))]} + nativeID={cancelButtonID} + onPress={deleteDraft} + hitSlop={{ + top: 3, + right: 3, + bottom: 3, + left: 3, + }} + > + {({hovered, pressed}) => ( + + )} + + + + + + { + textInputRef.current = el; + forwardedRef.current = el; + }} + nativeID={messageEditInput} + onChangeText={updateDraft} // Debounced saveDraftComment + onKeyPress={triggerSaveOrCancel} + value={draft} + maxLines={isSmallScreenWidth ? CONST.COMPOSER.MAX_LINES_SMALL_SCREEN : CONST.COMPOSER.MAX_LINES} // This is the same that slack has + style={[styles.textInputCompose, styles.flex1, styles.bgTransparent]} + onFocus={() => { + setIsFocused(true); + ReportScrollManager.scrollToIndex({animated: true, index}, true); + ComposerActions.setShouldShowComposeInput(false); + }} + onBlur={(event) => { + setIsFocused(false); + const relatedTargetId = lodashGet(event, 'nativeEvent.relatedTarget.id'); + + // Return to prevent re-render when save/cancel button is pressed which cancels the onPress event by re-rendering + if (_.contains([saveButtonID, cancelButtonID, emojiButtonID], relatedTargetId)) { + return; + } + + if (messageEditInput === relatedTargetId) { + return; + } + openReportActionComposeViewWhenClosingMessageEdit(); + }} + selection={selection} + onSelectionChange={onSelectionChange} + /> + + + InteractionManager.runAfterInteractions(() => textInputRef.current.focus())} + onEmojiSelected={addEmojiToTextBox} + nativeID={emojiButtonID} + /> + + + + + - {({hovered, pressed}) => ( - - )} - + + - - - { - this.textInput = el; - this.props.forwardedRef.current = el; - }} - nativeID={this.messageEditInput} - onChangeText={this.updateDraft} // Debounced saveDraftComment - onKeyPress={this.triggerSaveOrCancel} - value={this.state.draft} - maxLines={this.props.isSmallScreenWidth ? CONST.COMPOSER.MAX_LINES_SMALL_SCREEN : CONST.COMPOSER.MAX_LINES} // This is the same that slack has - style={[styles.textInputCompose, styles.flex1, styles.bgTransparent]} - onFocus={() => { - this.setState({isFocused: true}); - ReportScrollManager.scrollToIndex({animated: true, index: this.props.index}, true); - ComposerActions.setShouldShowComposeInput(false); - }} - onBlur={(event) => { - this.setState({isFocused: false}); - const relatedTargetId = lodashGet(event, 'nativeEvent.relatedTarget.id'); - - // Return to prevent re-render when save/cancel button is pressed which cancels the onPress event by re-rendering - if (_.contains([this.saveButtonID, this.cancelButtonID, this.emojiButtonID], relatedTargetId)) { - return; - } - - if (this.messageEditInput === relatedTargetId) { - return; - } - openReportActionComposeViewWhenClosingMessageEdit(); - }} - selection={this.state.selection} - onSelectionChange={this.onSelectionChange} - /> - - - InteractionManager.runAfterInteractions(() => this.textInput.focus())} - onEmojiSelected={this.addEmojiToTextBox} - nativeID={this.emojiButtonID} - /> - - - - - - - - - - - - - ); - } + + + + ); } ReportActionItemMessageEdit.propTypes = propTypes; From a925dd61962155f26fc7b9057943ae5ed5a2cc7d Mon Sep 17 00:00:00 2001 From: Rahul kushwaha Date: Wed, 7 Jun 2023 08:31:05 +0530 Subject: [PATCH 02/15] moved functions to inline --- .../report/ReportActionItemMessageEdit.js | 35 +++++-------------- 1 file changed, 8 insertions(+), 27 deletions(-) diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index ad349076ba5..492b368430a 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -74,6 +74,12 @@ const defaultProps = { preferredSkinTone: CONST.EMOJI_DEFAULT_SKIN_TONE, }; +// native ids +const saveButtonID = 'saveButton'; +const cancelButtonID = 'cancelButton'; +const emojiButtonID = 'emojiButton'; +const messageEditInput = 'messageEditInput'; + function ReportActionItemMessageEdit(props) { const {action, draftMessage, forwardedRef, index, isKeyboardShown, isSmallScreenWidth, preferredSkinTone, reportID, shouldDisableEmojiPicker, translate} = props; @@ -92,12 +98,6 @@ function ReportActionItemMessageEdit(props) { const textInputRef = useRef(null); - // native ids - const saveButtonID = 'saveButton'; - const cancelButtonID = 'cancelButton'; - const emojiButtonID = 'emojiButton'; - const messageEditInput = 'messageEditInput'; - useEffect(() => { // For mobile Safari, updating the selection prop on an unfocused input will cause it to automatically gain focus // and subsequent programmatic focus shifts (e.g., modal focus trap) to show the blue frame (:focus-visible style), @@ -126,25 +126,6 @@ function ReportActionItemMessageEdit(props) { [], ); - /** - * Update Selection on change cursor position. - * - * @param {Event} e - */ - const onSelectionChange = useCallback((e) => { - setSelection(e.nativeEvent.selection); - }, []); - - /** - * Updates the composer when the comment length is exceeded - * Shows red borders and prevents the comment from being sent - * - * @param {Boolean} hasExceeded - */ - const setExceededMaxCommentLength = useCallback((hasExceeded) => { - setHasExceededMaxCommentLength(hasExceeded); - }, []); - /** * Save the draft of the comment. This debounced so that we're not ceaselessly saving your edit. Saving the draft * allows one to navigate somewhere else and come back to the comment and still have it in edit mode. @@ -338,7 +319,7 @@ function ReportActionItemMessageEdit(props) { openReportActionComposeViewWhenClosingMessageEdit(); }} selection={selection} - onSelectionChange={onSelectionChange} + onSelectionChange={(e) => setSelection(e.nativeEvent.selection)} /> @@ -375,7 +356,7 @@ function ReportActionItemMessageEdit(props) { setHasExceededMaxCommentLength(hasExceeded)} /> ); From e7e59bfbf6b284cd05f8512b8cfd2d9977b4b321 Mon Sep 17 00:00:00 2001 From: Rahul kushwaha Date: Wed, 7 Jun 2023 08:37:56 +0530 Subject: [PATCH 03/15] moved to destructive props --- src/pages/home/report/ReportActionItemMessageEdit.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index 492b368430a..f48c585376e 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -80,9 +80,7 @@ const cancelButtonID = 'cancelButton'; const emojiButtonID = 'emojiButton'; const messageEditInput = 'messageEditInput'; -function ReportActionItemMessageEdit(props) { - const {action, draftMessage, forwardedRef, index, isKeyboardShown, isSmallScreenWidth, preferredSkinTone, reportID, shouldDisableEmojiPicker, translate} = props; - +function ReportActionItemMessageEdit({action, forwardedRef, draftMessage, index, isKeyboardShown, isSmallScreenWidth, preferredSkinTone, reportID, shouldDisableEmojiPicker, translate}) { const [draft, setDraft] = useState(() => { if (draftMessage === action.message[0].html) { // We only convert the report action message to markdown if the draft message is unchanged. @@ -291,6 +289,7 @@ function ReportActionItemMessageEdit(props) { multiline ref={(el) => { textInputRef.current = el; + // eslint-disable-next-line no-param-reassign forwardedRef.current = el; }} nativeID={messageEditInput} From 5f572610dec79ee5a3d8932e244f7df6de0ef4d9 Mon Sep 17 00:00:00 2001 From: Rahul kushwaha Date: Wed, 7 Jun 2023 21:22:07 +0530 Subject: [PATCH 04/15] revert from destructive props --- .../report/ReportActionItemMessageEdit.js | 49 ++++++++++--------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index f48c585376e..cbeae1f6bbf 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -80,15 +80,15 @@ const cancelButtonID = 'cancelButton'; const emojiButtonID = 'emojiButton'; const messageEditInput = 'messageEditInput'; -function ReportActionItemMessageEdit({action, forwardedRef, draftMessage, index, isKeyboardShown, isSmallScreenWidth, preferredSkinTone, reportID, shouldDisableEmojiPicker, translate}) { +function ReportActionItemMessageEdit(props) { const [draft, setDraft] = useState(() => { - if (draftMessage === action.message[0].html) { + if (props.draftMessage === props.action.message[0].html) { // We only convert the report action message to markdown if the draft message is unchanged. const parser = new ExpensiMark(); - return parser.htmlToMarkdown(draftMessage).trim(); + return parser.htmlToMarkdown(props.draftMessage).trim(); } // We need to decode saved draft message because it's escaped before saving. - return Str.htmlDecode(draftMessage); + return Str.htmlDecode(props.draftMessage); }); const [selection, setSelection] = useState({start: 0, end: 0}); const [isFocused, setIsFocused] = useState(false); @@ -132,9 +132,9 @@ function ReportActionItemMessageEdit({action, forwardedRef, draftMessage, index, const debouncedSaveDraft = useMemo( () => _.debounce((newDraft) => { - Report.saveReportActionDraft(reportID, action.reportActionID, newDraft); + Report.saveReportActionDraft(props.reportID, props.action.reportActionID, newDraft); }, 1000), - [reportID, action.reportActionID], + [props.reportID, props.action.reportActionID], ); /** @@ -144,7 +144,7 @@ function ReportActionItemMessageEdit({action, forwardedRef, draftMessage, index, */ const updateDraft = useCallback( (newDraftValue) => { - const {text: newDraft = '', emojis = []} = EmojiUtils.replaceEmojis(newDraftValue, isSmallScreenWidth, preferredSkinTone); + const {text: newDraft = '', emojis = []} = EmojiUtils.replaceEmojis(newDraftValue, props.isSmallScreenWidth, props.preferredSkinTone); if (!_.isEmpty(emojis)) { User.updateFrequentlyUsedEmojis(EmojiUtils.getFrequentlyUsedEmojis(emojis)); @@ -166,10 +166,10 @@ function ReportActionItemMessageEdit({action, forwardedRef, draftMessage, index, // We want to escape the draft message to differentiate the HTML from the report action and the HTML the user drafted. debouncedSaveDraft(_.escape(newDraft)); } else { - debouncedSaveDraft(action.message[0].html); + debouncedSaveDraft(props.action.message[0].html); } }, - [action.message, draft, debouncedSaveDraft, isSmallScreenWidth, preferredSkinTone], + [props.action.message, draft, debouncedSaveDraft, props.isSmallScreenWidth, props.preferredSkinTone], ); /** @@ -177,18 +177,18 @@ function ReportActionItemMessageEdit({action, forwardedRef, draftMessage, index, */ const deleteDraft = useCallback(() => { debouncedSaveDraft.cancel(); - Report.saveReportActionDraft(reportID, action.reportActionID, ''); + Report.saveReportActionDraft(props.reportID, props.action.reportActionID, ''); ComposerActions.setShouldShowComposeInput(true); ReportActionComposeFocusManager.focus(); // Scroll to the last comment after editing to make sure the whole comment is clearly visible in the report. - if (index === 0) { + if (props.index === 0) { const keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', () => { - ReportScrollManager.scrollToIndex({animated: true, index}, false); + ReportScrollManager.scrollToIndex({animated: true, index: props.index}, false); keyboardDidHideListener.remove(); }); } - }, [action.reportActionID, debouncedSaveDraft, index, reportID]); + }, [props.action.reportActionID, debouncedSaveDraft, props.index, props.reportID]); /** * Save the draft of the comment to be the new comment message. This will take the comment out of "edit mode" with @@ -208,12 +208,12 @@ function ReportActionItemMessageEdit({action, forwardedRef, draftMessage, index, // When user tries to save the empty message, it will delete it. Prompt the user to confirm deleting. if (!trimmedNewDraft) { - ReportActionContextMenu.showDeleteModal(reportID, action, false, deleteDraft, () => InteractionManager.runAfterInteractions(() => textInputRef.current.focus())); + ReportActionContextMenu.showDeleteModal(props.reportID, props.action, false, deleteDraft, () => InteractionManager.runAfterInteractions(() => textInputRef.current.focus())); return; } - Report.editReportComment(reportID, action, trimmedNewDraft); + Report.editReportComment(props.reportID, props.action, trimmedNewDraft); deleteDraft(); - }, [action, debouncedSaveDraft, deleteDraft, draft, reportID]); + }, [props.action, debouncedSaveDraft, deleteDraft, draft, props.reportID]); /** * @param {String} emoji @@ -236,7 +236,7 @@ function ReportActionItemMessageEdit({action, forwardedRef, draftMessage, index, */ const triggerSaveOrCancel = useCallback( (e) => { - if (!e || ComposerUtils.canSkipTriggerHotkeys(isSmallScreenWidth, isKeyboardShown)) { + if (!e || ComposerUtils.canSkipTriggerHotkeys(props.isSmallScreenWidth, props.isKeyboardShown)) { return; } if (e.key === CONST.KEYBOARD_SHORTCUTS.ENTER.shortcutKey && !e.shiftKey) { @@ -247,14 +247,14 @@ function ReportActionItemMessageEdit({action, forwardedRef, draftMessage, index, deleteDraft(); } }, - [deleteDraft, isKeyboardShown, isSmallScreenWidth, publishDraft], + [deleteDraft, props.isKeyboardShown, props.isSmallScreenWidth, publishDraft], ); return ( <> - + [styles.chatItemSubmitButton, StyleUtils.getButtonBackgroundColorStyle(getButtonState(hovered, pressed))]} nativeID={cancelButtonID} @@ -290,17 +290,17 @@ function ReportActionItemMessageEdit({action, forwardedRef, draftMessage, index, ref={(el) => { textInputRef.current = el; // eslint-disable-next-line no-param-reassign - forwardedRef.current = el; + props.forwardedRef.current = el; }} nativeID={messageEditInput} onChangeText={updateDraft} // Debounced saveDraftComment onKeyPress={triggerSaveOrCancel} value={draft} - maxLines={isSmallScreenWidth ? CONST.COMPOSER.MAX_LINES_SMALL_SCREEN : CONST.COMPOSER.MAX_LINES} // This is the same that slack has + maxLines={props.isSmallScreenWidth ? CONST.COMPOSER.MAX_LINES_SMALL_SCREEN : CONST.COMPOSER.MAX_LINES} // This is the same that slack has style={[styles.textInputCompose, styles.flex1, styles.bgTransparent]} onFocus={() => { setIsFocused(true); - ReportScrollManager.scrollToIndex({animated: true, index}, true); + ReportScrollManager.scrollToIndex({animated: true, index: props.index}, true); ComposerActions.setShouldShowComposeInput(false); }} onBlur={(event) => { @@ -323,7 +323,7 @@ function ReportActionItemMessageEdit({action, forwardedRef, draftMessage, index, InteractionManager.runAfterInteractions(() => textInputRef.current.focus())} onEmojiSelected={addEmojiToTextBox} nativeID={emojiButtonID} @@ -331,7 +331,7 @@ function ReportActionItemMessageEdit({action, forwardedRef, draftMessage, index, - + Date: Wed, 7 Jun 2023 21:26:06 +0530 Subject: [PATCH 05/15] function cleanup --- .../report/ReportActionItemMessageEdit.js | 58 +++++++++---------- 1 file changed, 27 insertions(+), 31 deletions(-) diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index cbeae1f6bbf..63f222835e0 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -107,10 +107,8 @@ function ReportActionItemMessageEdit(props) { }); return prevDraft; }); - }, []); - useEffect( - () => () => { + return () => { // Skip if this is not the focused message so the other edit composer stays focused. if (!isFocused) { return; @@ -119,10 +117,9 @@ function ReportActionItemMessageEdit(props) { // Show the main composer when the focused message is deleted from another client // to prevent the main composer stays hidden until we swtich to another chat. ComposerActions.setShouldShowComposeInput(true); - }, - // eslint-disable-next-line react-hooks/exhaustive-deps -- for willUnmount lifecycle - [], - ); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps -- we need it to call it once on mount + }, []); /** * Save the draft of the comment. This debounced so that we're not ceaselessly saving your edit. Saving the draft @@ -140,25 +137,27 @@ function ReportActionItemMessageEdit(props) { /** * Update the value of the draft in Onyx * - * @param {String} newDraftValue + * @param {String} newDraftInput */ const updateDraft = useCallback( - (newDraftValue) => { - const {text: newDraft = '', emojis = []} = EmojiUtils.replaceEmojis(newDraftValue, props.isSmallScreenWidth, props.preferredSkinTone); + (newDraftInput) => { + const {text: newDraft = '', emojis = []} = EmojiUtils.replaceEmojis(newDraftInput, props.isSmallScreenWidth, props.preferredSkinTone); if (!_.isEmpty(emojis)) { User.updateFrequentlyUsedEmojis(EmojiUtils.getFrequentlyUsedEmojis(emojis)); } - if (newDraftValue !== newDraft) { - setSelection((prevSelection) => { - const remainder = draft.slice(prevSelection.end).length; - return { - start: newDraft.length - remainder, - end: newDraft.length - remainder, - }; - }); - } - setDraft(newDraft); + setDraft((prevDraft) => { + if (newDraftInput !== newDraft) { + setSelection((prevSelection) => { + const remainder = prevDraft.slice(prevSelection.end).length; + return { + start: newDraft.length - remainder, + end: newDraft.length - remainder, + }; + }); + } + return newDraft; + }); // This component is rendered only when draft is set to a non-empty string. In order to prevent component // unmount when user deletes content of textarea, we set previous message instead of empty string. @@ -169,7 +168,7 @@ function ReportActionItemMessageEdit(props) { debouncedSaveDraft(props.action.message[0].html); } }, - [props.action.message, draft, debouncedSaveDraft, props.isSmallScreenWidth, props.preferredSkinTone], + [props.action.message, debouncedSaveDraft, props.isSmallScreenWidth, props.preferredSkinTone], ); /** @@ -218,16 +217,13 @@ function ReportActionItemMessageEdit(props) { /** * @param {String} emoji */ - const addEmojiToTextBox = useCallback( - (emoji) => { - setSelection((prevSelection) => ({ - start: prevSelection.start + emoji.length, - end: prevSelection.start + emoji.length, - })); - updateDraft(ComposerUtils.insertText(draft, selection, emoji)); - }, - [draft, selection, updateDraft], - ); + const addEmojiToTextBox = (emoji) => { + setSelection((prevSelection) => ({ + start: prevSelection.start + emoji.length, + end: prevSelection.start + emoji.length, + })); + updateDraft(ComposerUtils.insertText(draft, selection, emoji)); + }; /** * Key event handlers that short cut to saving/canceling. From d13c04f0fc483f52be6fad23983265893589d3ac Mon Sep 17 00:00:00 2001 From: Rahul kushwaha Date: Thu, 8 Jun 2023 10:28:10 +0530 Subject: [PATCH 06/15] use textInput ref isFocused method instead of isFocused --- src/pages/home/report/ReportActionItemMessageEdit.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index 63f222835e0..58345a885bc 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -109,8 +109,8 @@ function ReportActionItemMessageEdit(props) { }); return () => { - // Skip if this is not the focused message so the other edit composer stays focused. - if (!isFocused) { + // Skip if this is not the focused message so the other edit composer stays focused + if (!textInputRef.current.isFocused()) { return; } From b31e76f8ff934eafc174486df2b26a9f7757403b Mon Sep 17 00:00:00 2001 From: Rahul kushwaha Date: Mon, 12 Jun 2023 19:31:18 +0530 Subject: [PATCH 07/15] use isFocusedRef for keeping latest value --- src/pages/home/report/ReportActionItemMessageEdit.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index 58345a885bc..c0f3d0d592d 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -95,6 +95,12 @@ function ReportActionItemMessageEdit(props) { const [hasExceededMaxCommentLength, setHasExceededMaxCommentLength] = useState(false); const textInputRef = useRef(null); + const isFocusedRef = useRef(false); + + useEffect(() => { + // required for keeping last state of isFocused variable + isFocusedRef.current = isFocused; + }, [isFocused]); useEffect(() => { // For mobile Safari, updating the selection prop on an unfocused input will cause it to automatically gain focus @@ -110,7 +116,7 @@ function ReportActionItemMessageEdit(props) { return () => { // Skip if this is not the focused message so the other edit composer stays focused - if (!textInputRef.current.isFocused()) { + if (!isFocusedRef.current) { return; } From 5467cd0b257e5d6b5102888a5751813d5f529963 Mon Sep 17 00:00:00 2001 From: Rahul kushwaha Date: Mon, 12 Jun 2023 21:14:40 +0530 Subject: [PATCH 08/15] removed comment --- src/pages/home/report/ReportActionItemMessageEdit.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index c0f3d0d592d..cb50ad9f6f2 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -124,7 +124,6 @@ function ReportActionItemMessageEdit(props) { // to prevent the main composer stays hidden until we swtich to another chat. ComposerActions.setShouldShowComposeInput(true); }; - // eslint-disable-next-line react-hooks/exhaustive-deps -- we need it to call it once on mount }, []); /** From 159c8597db94474678f985ca6c6fa46657539716 Mon Sep 17 00:00:00 2001 From: Rahul kushwaha Date: Wed, 14 Jun 2023 01:10:15 +0530 Subject: [PATCH 09/15] using usePrevious hook instead of direct ref --- src/pages/home/report/ReportActionItemMessageEdit.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index a640ca011e1..36ca2c5f116 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -36,6 +36,7 @@ import * as ComposerActions from '../../../libs/actions/Composer'; import * as User from '../../../libs/actions/User'; import PressableWithFeedback from '../../../components/Pressable/PressableWithFeedback'; import Hoverable from '../../../components/Hoverable'; +import usePrevious from '../../../hooks/usePrevious' const propTypes = { /** All the data of the action */ @@ -96,12 +97,8 @@ function ReportActionItemMessageEdit(props) { const [hasExceededMaxCommentLength, setHasExceededMaxCommentLength] = useState(false); const textInputRef = useRef(null); - const isFocusedRef = useRef(false); + const wasPreviouslyFocused = usePrevious(isFocused); - useEffect(() => { - // required for keeping last state of isFocused variable - isFocusedRef.current = isFocused; - }, [isFocused]); useEffect(() => { // For mobile Safari, updating the selection prop on an unfocused input will cause it to automatically gain focus @@ -117,7 +114,7 @@ function ReportActionItemMessageEdit(props) { return () => { // Skip if this is not the focused message so the other edit composer stays focused - if (!isFocusedRef.current) { + if (wasPreviouslyFocused) { return; } @@ -125,6 +122,7 @@ function ReportActionItemMessageEdit(props) { // to prevent the main composer stays hidden until we swtich to another chat. ComposerActions.setShouldShowComposeInput(true); }; + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); /** @@ -262,6 +260,7 @@ function ReportActionItemMessageEdit(props) { Date: Wed, 14 Jun 2023 01:15:27 +0530 Subject: [PATCH 10/15] prettier --- src/pages/home/report/ReportActionItemMessageEdit.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index 36ca2c5f116..d1842c9fa75 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -36,7 +36,7 @@ import * as ComposerActions from '../../../libs/actions/Composer'; import * as User from '../../../libs/actions/User'; import PressableWithFeedback from '../../../components/Pressable/PressableWithFeedback'; import Hoverable from '../../../components/Hoverable'; -import usePrevious from '../../../hooks/usePrevious' +import usePrevious from '../../../hooks/usePrevious'; const propTypes = { /** All the data of the action */ @@ -99,7 +99,6 @@ function ReportActionItemMessageEdit(props) { const textInputRef = useRef(null); const wasPreviouslyFocused = usePrevious(isFocused); - useEffect(() => { // For mobile Safari, updating the selection prop on an unfocused input will cause it to automatically gain focus // and subsequent programmatic focus shifts (e.g., modal focus trap) to show the blue frame (:focus-visible style), @@ -122,7 +121,7 @@ function ReportActionItemMessageEdit(props) { // to prevent the main composer stays hidden until we swtich to another chat. ComposerActions.setShouldShowComposeInput(true); }; - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); /** From fe3cc67c689cdab36233bde940a19d2380a2821b Mon Sep 17 00:00:00 2001 From: Rahul kushwaha Date: Wed, 14 Jun 2023 02:02:48 +0530 Subject: [PATCH 11/15] added useKeyboardState hook --- src/hooks/useKeyboardState.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/hooks/useKeyboardState.js diff --git a/src/hooks/useKeyboardState.js b/src/hooks/useKeyboardState.js new file mode 100644 index 00000000000..cebb624ea09 --- /dev/null +++ b/src/hooks/useKeyboardState.js @@ -0,0 +1,23 @@ +import {useEffect, useState} from 'react'; +import {Keyboard} from 'react-native'; + +/** + * Hook for getting current state of keyboard + * whether or not the keyboard is open + * @returns {Object} + */ +export default function useKeyboardState() { + const [isKeyboardShown, setIsKeyboardShown] = useState(false); + + useEffect(() => { + const keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', () => setIsKeyboardShown(true)); + return keyboardDidShowListener.remove; + }, []); + + useEffect(() => { + const keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', () => setIsKeyboardShown(false)); + return keyboardDidHideListener.remove; + }, []); + + return {isKeyboardShown}; +} From fe6a826e17983ef8a1cc968e20d8d4adf9a75757 Mon Sep 17 00:00:00 2001 From: Rahul kushwaha Date: Wed, 14 Jun 2023 02:03:30 +0530 Subject: [PATCH 12/15] repalced usage of HOC with hooks --- .../report/ReportActionItemMessageEdit.js | 55 ++++++++----------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index d1842c9fa75..a93239fb358 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -1,7 +1,6 @@ /* eslint-disable rulesdir/onyx-props-must-have-default */ import lodashGet from 'lodash/get'; import React, {useState, useRef, useMemo, useEffect, useCallback} from 'react'; -// eslint-disable-next-line no-restricted-imports import {InteractionManager, Keyboard, View} from 'react-native'; import PropTypes from 'prop-types'; import _ from 'underscore'; @@ -16,7 +15,6 @@ import * as Report from '../../../libs/actions/Report'; import * as ReportScrollManager from '../../../libs/ReportScrollManager'; import openReportActionComposeViewWhenClosingMessageEdit from '../../../libs/openReportActionComposeViewWhenClosingMessageEdit'; import ReportActionComposeFocusManager from '../../../libs/ReportActionComposeFocusManager'; -import compose from '../../../libs/compose'; import EmojiPickerButton from '../../../components/EmojiPicker/EmojiPickerButton'; import Icon from '../../../components/Icon'; import * as Expensicons from '../../../components/Icon/Expensicons'; @@ -27,9 +25,6 @@ import * as EmojiUtils from '../../../libs/EmojiUtils'; import reportPropTypes from '../../reportPropTypes'; import ExceededCommentLength from '../../../components/ExceededCommentLength'; import CONST from '../../../CONST'; -import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions'; -import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; -import withKeyboardState, {keyboardStatePropTypes} from '../../../components/withKeyboardState'; import refPropTypes from '../../../components/refPropTypes'; import * as ComposerUtils from '../../../libs/ComposerUtils'; import * as ComposerActions from '../../../libs/actions/Composer'; @@ -37,6 +32,9 @@ import * as User from '../../../libs/actions/User'; import PressableWithFeedback from '../../../components/Pressable/PressableWithFeedback'; import Hoverable from '../../../components/Hoverable'; import usePrevious from '../../../hooks/usePrevious'; +import useLocalize from '../../../hooks/useLocalize'; +import useKeyboardState from '../../../hooks/useKeyboardState'; +import useWindowDimensions from '../../../hooks/useWindowDimensions'; const propTypes = { /** All the data of the action */ @@ -63,10 +61,6 @@ const propTypes = { /** Stores user's preferred skin tone */ preferredSkinTone: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - - ...withLocalizePropTypes, - ...windowDimensionsPropTypes, - ...keyboardStatePropTypes, }; const defaultProps = { @@ -83,6 +77,10 @@ const emojiButtonID = 'emojiButton'; const messageEditInput = 'messageEditInput'; function ReportActionItemMessageEdit(props) { + const {translate} = useLocalize(); + const {isKeyboardShown} = useKeyboardState(); + const {isSmallScreenWidth} = useWindowDimensions(); + const [draft, setDraft] = useState(() => { if (props.draftMessage === props.action.message[0].html) { // We only convert the report action message to markdown if the draft message is unchanged. @@ -144,7 +142,7 @@ function ReportActionItemMessageEdit(props) { */ const updateDraft = useCallback( (newDraftInput) => { - const {text: newDraft = '', emojis = []} = EmojiUtils.replaceEmojis(newDraftInput, props.isSmallScreenWidth, props.preferredSkinTone); + const {text: newDraft = '', emojis = []} = EmojiUtils.replaceEmojis(newDraftInput, isSmallScreenWidth, props.preferredSkinTone); if (!_.isEmpty(emojis)) { User.updateFrequentlyUsedEmojis(EmojiUtils.getFrequentlyUsedEmojis(emojis)); @@ -171,7 +169,7 @@ function ReportActionItemMessageEdit(props) { debouncedSaveDraft(props.action.message[0].html); } }, - [props.action.message, debouncedSaveDraft, props.isSmallScreenWidth, props.preferredSkinTone], + [props.action.message, debouncedSaveDraft, isSmallScreenWidth, props.preferredSkinTone], ); /** @@ -235,7 +233,7 @@ function ReportActionItemMessageEdit(props) { */ const triggerSaveOrCancel = useCallback( (e) => { - if (!e || ComposerUtils.canSkipTriggerHotkeys(props.isSmallScreenWidth, props.isKeyboardShown)) { + if (!e || ComposerUtils.canSkipTriggerHotkeys(isSmallScreenWidth, isKeyboardShown)) { return; } if (e.key === CONST.KEYBOARD_SHORTCUTS.ENTER.shortcutKey && !e.shiftKey) { @@ -246,14 +244,14 @@ function ReportActionItemMessageEdit(props) { deleteDraft(); } }, - [deleteDraft, props.isKeyboardShown, props.isSmallScreenWidth, publishDraft], + [deleteDraft, isKeyboardShown, isSmallScreenWidth, publishDraft], ); return ( <> - + {(hovered) => ( { setIsFocused(true); @@ -333,14 +331,14 @@ function ReportActionItemMessageEdit(props) { - + @@ -364,16 +362,11 @@ function ReportActionItemMessageEdit(props) { ReportActionItemMessageEdit.propTypes = propTypes; ReportActionItemMessageEdit.defaultProps = defaultProps; ReportActionItemMessageEdit.displayName = 'ReportActionItemMessageEdit'; -export default compose( - withLocalize, - withWindowDimensions, - withKeyboardState, -)( - React.forwardRef((props, ref) => ( - - )), -); + +export default React.forwardRef((props, ref) => ( + +)); From 17469608ac5060d37c4c58a38cc924f65b90507c Mon Sep 17 00:00:00 2001 From: Rahul kushwaha Date: Wed, 14 Jun 2023 02:26:34 +0530 Subject: [PATCH 13/15] updated useKeyboardState hook to use KeyboardStateContext --- src/components/withKeyboardState.js | 2 +- src/hooks/useKeyboardState.js | 19 +++---------------- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/src/components/withKeyboardState.js b/src/components/withKeyboardState.js index c5a72f75200..667e8865a0e 100755 --- a/src/components/withKeyboardState.js +++ b/src/components/withKeyboardState.js @@ -68,4 +68,4 @@ export default function withKeyboardState(WrappedComponent) { return WithKeyboardState; } -export {KeyboardStateProvider, keyboardStatePropTypes}; +export {KeyboardStateProvider, keyboardStatePropTypes, KeyboardStateContext}; diff --git a/src/hooks/useKeyboardState.js b/src/hooks/useKeyboardState.js index cebb624ea09..064853d1814 100644 --- a/src/hooks/useKeyboardState.js +++ b/src/hooks/useKeyboardState.js @@ -1,23 +1,10 @@ -import {useEffect, useState} from 'react'; -import {Keyboard} from 'react-native'; - +import {useContext} from 'react'; +import {KeyboardStateContext} from '../components/withKeyboardState'; /** * Hook for getting current state of keyboard * whether or not the keyboard is open * @returns {Object} */ export default function useKeyboardState() { - const [isKeyboardShown, setIsKeyboardShown] = useState(false); - - useEffect(() => { - const keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', () => setIsKeyboardShown(true)); - return keyboardDidShowListener.remove; - }, []); - - useEffect(() => { - const keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', () => setIsKeyboardShown(false)); - return keyboardDidHideListener.remove; - }, []); - - return {isKeyboardShown}; + return useContext(KeyboardStateContext); } From b5c9058ab0d0b7f1700b69ae342df2b0ea003842 Mon Sep 17 00:00:00 2001 From: Rahul kushwaha Date: Wed, 14 Jun 2023 11:13:40 +0530 Subject: [PATCH 14/15] revert usage of usePrevious hook --- src/pages/home/report/ReportActionItemMessageEdit.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index a93239fb358..c5b092487d7 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -1,4 +1,3 @@ -/* eslint-disable rulesdir/onyx-props-must-have-default */ import lodashGet from 'lodash/get'; import React, {useState, useRef, useMemo, useEffect, useCallback} from 'react'; import {InteractionManager, Keyboard, View} from 'react-native'; @@ -31,7 +30,6 @@ import * as ComposerActions from '../../../libs/actions/Composer'; import * as User from '../../../libs/actions/User'; import PressableWithFeedback from '../../../components/Pressable/PressableWithFeedback'; import Hoverable from '../../../components/Hoverable'; -import usePrevious from '../../../hooks/usePrevious'; import useLocalize from '../../../hooks/useLocalize'; import useKeyboardState from '../../../hooks/useKeyboardState'; import useWindowDimensions from '../../../hooks/useWindowDimensions'; @@ -95,7 +93,12 @@ function ReportActionItemMessageEdit(props) { const [hasExceededMaxCommentLength, setHasExceededMaxCommentLength] = useState(false); const textInputRef = useRef(null); - const wasPreviouslyFocused = usePrevious(isFocused); + const isFocusedRef = useRef(false); + + useEffect(() => { + // required for keeping last state of isFocused variable + isFocusedRef.current = isFocused; + }, [isFocused]); useEffect(() => { // For mobile Safari, updating the selection prop on an unfocused input will cause it to automatically gain focus @@ -111,7 +114,7 @@ function ReportActionItemMessageEdit(props) { return () => { // Skip if this is not the focused message so the other edit composer stays focused - if (wasPreviouslyFocused) { + if (!isFocusedRef.current) { return; } @@ -119,7 +122,6 @@ function ReportActionItemMessageEdit(props) { // to prevent the main composer stays hidden until we swtich to another chat. ComposerActions.setShouldShowComposeInput(true); }; - // eslint-disable-next-line react-hooks/exhaustive-deps }, []); /** From ddf049b3a63d67c0da2bdef8cae343b28c74792f Mon Sep 17 00:00:00 2001 From: Rahul kushwaha Date: Wed, 14 Jun 2023 15:22:41 +0530 Subject: [PATCH 15/15] added empty line after imports --- src/hooks/useKeyboardState.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hooks/useKeyboardState.js b/src/hooks/useKeyboardState.js index 064853d1814..8b57fb60f2b 100644 --- a/src/hooks/useKeyboardState.js +++ b/src/hooks/useKeyboardState.js @@ -1,5 +1,6 @@ import {useContext} from 'react'; import {KeyboardStateContext} from '../components/withKeyboardState'; + /** * Hook for getting current state of keyboard * whether or not the keyboard is open