Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate ComposerWithSuggestions to useOnyx #51573

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/pages/home/ReportScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,9 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro
const lastRoute = usePrevious(route);
const lastReportActionIDFromRoute = usePrevious(reportActionIDFromRoute);

const onComposerFocus = useCallback(() => setIsComposerFocus(true), []);
const onComposerBlur = useCallback(() => setIsComposerFocus(false), []);

// Define here because reportActions are recalculated before mount, allowing data to display faster than useEffect can trigger.
// If we have cached reportActions, they will be shown immediately.
// We aim to display a loader first, then fetch relevant reportActions, and finally show them.
Expand Down Expand Up @@ -822,8 +825,8 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro

{isCurrentReportLoadedFromOnyx ? (
<ReportFooter
onComposerFocus={() => setIsComposerFocus(true)}
onComposerBlur={() => setIsComposerFocus(false)}
onComposerFocus={onComposerFocus}
onComposerBlur={onComposerBlur}
report={report}
reportMetadata={reportMetadata}
policy={policy}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {useIsFocused, useNavigation} from '@react-navigation/native';
import lodashDebounce from 'lodash/debounce';
import type {ForwardedRef, MutableRefObject, RefAttributes, RefObject} from 'react';
import type {ForwardedRef, MutableRefObject, RefObject} from 'react';
import React, {forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
import type {
LayoutChangeEvent,
Expand All @@ -14,7 +14,7 @@ import type {
import {DeviceEventEmitter, findNodeHandle, InteractionManager, NativeModules, View} from 'react-native';
import {useFocusedInputHandler} from 'react-native-keyboard-controller';
import type {OnyxEntry} from 'react-native-onyx';
import {useOnyx, withOnyx} from 'react-native-onyx';
import {useOnyx} from 'react-native-onyx';
import {useAnimatedRef, useSharedValue} from 'react-native-reanimated';
import type {Emoji} from '@assets/emojis/types';
import type {FileObject} from '@components/AttachmentModal';
Expand Down Expand Up @@ -65,113 +65,95 @@ type SyncSelection = {

type NewlyAddedChars = {startIndex: number; endIndex: number; diff: string};

type ComposerWithSuggestionsOnyxProps = {
/** The parent report actions for the report */
parentReportActions: OnyxEntry<OnyxTypes.ReportActions>;
type ComposerWithSuggestionsProps = Partial<ChildrenProps> & {
/** Report ID */
reportID: string;

/** The modal state */
modal: OnyxEntry<OnyxTypes.Modal>;
/** Callback to focus composer */
onFocus: () => void;

/** The preferred skin tone of the user */
preferredSkinTone: number;
/** Callback to blur composer */
onBlur: (event: NativeSyntheticEvent<TextInputFocusEventData>) => void;

/** Whether the input is focused */
editFocused: OnyxEntry<boolean>;
};

type ComposerWithSuggestionsProps = ComposerWithSuggestionsOnyxProps &
Partial<ChildrenProps> & {
/** Report ID */
reportID: string;

/** Callback to focus composer */
onFocus: () => void;

/** Callback to blur composer */
onBlur: (event: NativeSyntheticEvent<TextInputFocusEventData>) => void;

/** Callback when layout of composer changes */
onLayout?: (event: LayoutChangeEvent) => void;
/** Callback when layout of composer changes */
onLayout?: (event: LayoutChangeEvent) => void;

/** Callback to update the value of the composer */
onValueChange: (value: string) => void;
/** Callback to update the value of the composer */
onValueChange: (value: string) => void;

/** Callback when the composer got cleared on the UI thread */
onCleared?: (text: string) => void;
/** Callback when the composer got cleared on the UI thread */
onCleared?: (text: string) => void;

/** Whether the composer is full size */
isComposerFullSize: boolean;
/** Whether the composer is full size */
isComposerFullSize: boolean;

/** Whether the menu is visible */
isMenuVisible: boolean;
/** Whether the menu is visible */
isMenuVisible: boolean;

/** The placeholder for the input */
inputPlaceholder: string;
/** The placeholder for the input */
inputPlaceholder: string;

/** Function to display a file in a modal */
displayFileInModal: (file: FileObject) => void;
/** Function to display a file in a modal */
displayFileInModal: (file: FileObject) => void;

/** Whether the user is blocked from concierge */
isBlockedFromConcierge: boolean;
/** Whether the user is blocked from concierge */
isBlockedFromConcierge: boolean;

/** Whether the input is disabled */
disabled: boolean;
/** Whether the input is disabled */
disabled: boolean;

/** Whether the full composer is available */
isFullComposerAvailable: boolean;
/** Whether the full composer is available */
isFullComposerAvailable: boolean;

/** Function to set whether the full composer is available */
setIsFullComposerAvailable: (isFullComposerAvailable: boolean) => void;
/** Function to set whether the full composer is available */
setIsFullComposerAvailable: (isFullComposerAvailable: boolean) => void;

/** Function to set whether the comment is empty */
setIsCommentEmpty: (isCommentEmpty: boolean) => void;
/** Function to set whether the comment is empty */
setIsCommentEmpty: (isCommentEmpty: boolean) => void;

/** Function to handle sending a message */
handleSendMessage: () => void;
/** Function to handle sending a message */
handleSendMessage: () => void;

/** Whether the compose input should show */
shouldShowComposeInput: OnyxEntry<boolean>;
/** Whether the compose input should show */
shouldShowComposeInput: OnyxEntry<boolean>;

/** Function to measure the parent container */
measureParentContainer: (callback: MeasureInWindowOnSuccessCallback) => void;
/** Function to measure the parent container */
measureParentContainer: (callback: MeasureInWindowOnSuccessCallback) => void;

/** Whether the scroll is likely to trigger a layout */
isScrollLikelyLayoutTriggered: RefObject<boolean>;
/** Whether the scroll is likely to trigger a layout */
isScrollLikelyLayoutTriggered: RefObject<boolean>;

/** Function to raise the scroll is likely layout triggered */
raiseIsScrollLikelyLayoutTriggered: () => void;
/** Function to raise the scroll is likely layout triggered */
raiseIsScrollLikelyLayoutTriggered: () => void;

/** The ref to the suggestions */
suggestionsRef: React.RefObject<SuggestionsRef>;
/** The ref to the suggestions */
suggestionsRef: React.RefObject<SuggestionsRef>;

/** The ref to the next modal will open */
isNextModalWillOpenRef: MutableRefObject<boolean | null>;
/** The ref to the next modal will open */
isNextModalWillOpenRef: MutableRefObject<boolean | null>;

/** Whether the edit is focused */
editFocused: boolean;
/** Wheater chat is empty */
isEmptyChat?: boolean;

/** Wheater chat is empty */
isEmptyChat?: boolean;
/** The last report action */
lastReportAction?: OnyxEntry<OnyxTypes.ReportAction>;

/** The last report action */
lastReportAction?: OnyxEntry<OnyxTypes.ReportAction>;
/** Whether to include chronos */
includeChronos?: boolean;

/** Whether to include chronos */
includeChronos?: boolean;
/** The parent report action ID */
parentReportActionID?: string;

/** The parent report action ID */
parentReportActionID?: string;
/** The parent report ID */
// eslint-disable-next-line react/no-unused-prop-types -- its used in the withOnyx HOC
parentReportID: string | undefined;

/** The parent report ID */
// eslint-disable-next-line react/no-unused-prop-types -- its used in the withOnyx HOC
parentReportID: string | undefined;
/** Whether report is from group policy */
isGroupPolicyReport: boolean;

/** Whether report is from group policy */
isGroupPolicyReport: boolean;

/** policy ID of the report */
policyID: string;
};
/** policy ID of the report */
policyID: string;
};

type SwitchToCurrentReportProps = {
preexistingReportID: string;
Expand Down Expand Up @@ -223,13 +205,9 @@ const shouldFocusInputOnScreenFocus = canFocusInputOnScreenFocus();
*/
function ComposerWithSuggestions(
{
// Onyx
modal,
preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE,
parentReportActions,

// Props: Report
reportID,
parentReportID,
includeChronos,
isEmptyChat,
lastReportAction,
Expand Down Expand Up @@ -263,7 +241,6 @@ function ComposerWithSuggestions(
// Refs
suggestionsRef,
isNextModalWillOpenRef,
editFocused,

// For testing
children,
Expand All @@ -290,6 +267,12 @@ function ComposerWithSuggestions(
});
const commentRef = useRef(value);

const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`);
const [modal] = useOnyx(ONYXKEYS.MODAL);
const [preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE] = useOnyx(ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, {selector: EmojiUtils.getPreferredSkinToneIndex});
const [editFocused] = useOnyx(ONYXKEYS.INPUT_FOCUSED);
const [parentReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`, {canEvict: false, initWithStoredValues: false});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@blazejkustra I think we have a blocker issue here if parentReportID is undefined in which case the app will crash. We can reproduce this if we search for a chat with a user for whom there is no report present locally. Here is a screenshot of the crash.

Screenshot 2024-11-04 at 10 26 55 PM

@lakchote Not sure if we can have a fix soon for this. Otherwise we will have to revert the PR as this will be a blocker. Sorry for the inconvenience caused.


const lastTextRef = useRef(value);
useEffect(() => {
lastTextRef.current = value;
Expand All @@ -298,8 +281,7 @@ function ComposerWithSuggestions(
const {shouldUseNarrowLayout} = useResponsiveLayout();
const maxComposerLines = shouldUseNarrowLayout ? CONST.COMPOSER.MAX_LINES_SMALL_SCREEN : CONST.COMPOSER.MAX_LINES;

const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`);
const parentReportAction = parentReportActions?.[parentReportActionID ?? '-1'];
const parentReportAction = useMemo(() => parentReportActions?.[parentReportActionID ?? '-1'], [parentReportActionID, parentReportActions]);
const shouldAutoFocus =
!modal?.isVisible &&
Modal.areAllModalsHidden() &&
Expand Down Expand Up @@ -653,6 +635,7 @@ function ComposerWithSuggestions(

const prevIsModalVisible = usePrevious(modal?.isVisible);
const prevIsFocused = usePrevious(isFocused);

useEffect(() => {
if (modal?.isVisible && !prevIsModalVisible) {
// eslint-disable-next-line react-compiler/react-compiler, no-param-reassign
Expand Down Expand Up @@ -683,6 +666,7 @@ function ComposerWithSuggestions(
updateMultilineInputRange(textInputRef.current, !!shouldAutoFocus);
// eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
}, []);

useImperativeHandle(
ref,
() => ({
Expand Down Expand Up @@ -836,24 +820,6 @@ function ComposerWithSuggestions(

ComposerWithSuggestions.displayName = 'ComposerWithSuggestions';

const ComposerWithSuggestionsWithRef = forwardRef(ComposerWithSuggestions);

export default withOnyx<ComposerWithSuggestionsProps & RefAttributes<ComposerRef>, ComposerWithSuggestionsOnyxProps>({
modal: {
key: ONYXKEYS.MODAL,
},
preferredSkinTone: {
key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE,
selector: EmojiUtils.getPreferredSkinToneIndex,
},
editFocused: {
key: ONYXKEYS.INPUT_FOCUSED,
},
parentReportActions: {
key: ({parentReportID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`,
canEvict: false,
initWithStoredValues: false,
},
})(memo(ComposerWithSuggestionsWithRef));
export default memo(forwardRef(ComposerWithSuggestions));

export type {ComposerWithSuggestionsProps, ComposerRef};
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,16 @@ function ReportActionCompose({
],
);

const onValueChange = useCallback(
(value: string) => {
if (value.length === 0 && isComposerFullSize) {
Report.setIsComposerFullSize(reportID, false);
}
validateCommentMaxLength(value, {reportID});
},
[isComposerFullSize, reportID, validateCommentMaxLength],
);

return (
<View style={[shouldShowReportRecipientLocalTime && !isOffline && styles.chatItemComposeWithFirstRow, isComposerFullSize && styles.chatItemFullComposeRow]}>
<OfflineWithFeedback pendingAction={pendingAction}>
Expand Down Expand Up @@ -490,12 +500,7 @@ function ReportActionCompose({
onFocus={onFocus}
onBlur={onBlur}
measureParentContainer={measureContainer}
onValueChange={(value) => {
if (value.length === 0 && isComposerFullSize) {
Report.setIsComposerFullSize(reportID, false);
}
validateCommentMaxLength(value, {reportID});
}}
onValueChange={onValueChange}
/>
<ReportDropUI
onDrop={(event: DragEvent) => {
Expand Down
Loading