Skip to content
Merged
58 changes: 29 additions & 29 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"words": [
"--longpress",
"Accelo",
"accountid",
"achreimburse",
"actool",
"adbd",
Expand Down Expand Up @@ -43,6 +44,7 @@
"approvalstatus",
"appversion",
"archivado",
"Areport",
"ARGB",
"armeabi",
"armv7",
Expand All @@ -57,7 +59,6 @@
"asar",
"ASPAC",
"assetlinks",
"accountid",
"attributes.accountid",
"attributes.reportid",
"authorised",
Expand Down Expand Up @@ -104,17 +105,17 @@
"Buildscript",
"Bushwick",
"BYOC",
"cacerts",
"capitalone",
"CAROOT",
"Carta",
"cacerts",
"changeit",
"ccache",
"ccupload",
"cdfbmo",
"Certinia",
"Certinia's",
"CFPB",
"changeit",
"chargeback",
"Charleson",
"Checkmark",
Expand All @@ -140,6 +141,7 @@
"contenteditable",
"copiloted",
"copiloting",
"copyable",
"Corpay",
"Countertop",
"CPPFLAGS",
Expand All @@ -152,13 +154,12 @@
"customfield",
"customise",
"dateexported",
"deapex",
"deapexer",
"debitamount",
"deburr",
"deburred",
"REJECTEDTRANSACTION",
"Deel",
"deapex",
"deapexer",
"deeplink",
"deeplinked",
"deeplinking",
Expand All @@ -183,7 +184,9 @@
"Drycleaning",
"DSYM",
"dsyms",
"Dtype",
"durationMillis",
"DYNAMICEXTERNAL",
"e2edelta",
"ecash",
"ecconnrefused",
Expand Down Expand Up @@ -223,12 +226,12 @@
"Expensable",
"expensescount",
"Expensi",
"Expensidev",
"Expensicon",
"Expensicons",
"expensicorp",
"Expensifier",
"Expensidev",
"EXPENSIDEV",
"Expensifier",
"EXPENSIFYAPI",
"expensifyhelp",
"expensifylite",
Expand Down Expand Up @@ -348,8 +351,8 @@
"keyvaluepairs",
"KHTML",
"killall",
"kilometres",
"kilometre",
"kilometres",
"Kort",
"Kowalski",
"Krasoń",
Expand Down Expand Up @@ -407,6 +410,9 @@
"Mobasher",
"mobiexpensifyg",
"mobileprovision",
"moveElemsAttrsToGroup",
"moveGroupAttrsToElems",
"mple",
"mswin",
"msword",
"mtrl",
Expand Down Expand Up @@ -452,9 +458,9 @@
"Nonstore",
"Nonupholstered",
"noopener",
"noprompt",
"noreferer",
"noreferrer",
"noprompt",
"nosymbol",
"Noto",
"NSQS",
Expand All @@ -477,6 +483,7 @@
"OLDDOT",
"onclosetag",
"Oncorp",
"oneline",
"oneteam",
"oneui",
"onfido",
Expand Down Expand Up @@ -577,8 +584,10 @@
"REIMBURSER",
"reimbursible",
"Reimbursments",
"REJECTEDTRANSACTION",
"remotedesktop",
"remotesync",
"removeHiddenElems",
"REPORTPREVIEW",
"requestee",
"Resawing",
Expand All @@ -587,11 +596,12 @@
"retryable",
"Reupholstery",
"rideshare",
"rock",
"RNCORE",
"RNFS",
"rnmapbox",
"RNTL",
"RNVP",
"rock",
"Roni",
"Rosiclair",
"rpartition",
Expand All @@ -611,40 +621,43 @@
"Schengen",
"Schiffli",
"SCIM",
"sdkmanager",
"scriptname",
"sdkmanager",
"seamless",
"Segoe",
"seguiemj",
"Selec",
"Sepa",
"serveo",
"setuptools",
"Sharees",
"Sharons",
"shellcheck",
"shellenv",
"Skydo",
"shipit",
"shouldshowellipsis",
"signingkey",
"signup",
"Signup",
"simctl",
"skip_codesigning",
"Skydo",
"Slurper",
"SMARTREPORT",
"Smartscan",
"soloader",
"SONIFICATION",
"Speedscope",
"Splittable",
"Spotnana",
"spreadsheetml",
"srgb",
"SSAE",
"stackoverflow",
"startdate",
"stdev",
"stdlib",
"storepass",
"stackoverflow",
"STORYLANE",
"strikethrough",
"Strikethrough",
Expand Down Expand Up @@ -752,6 +765,7 @@
"webrtc",
"welldone",
"Woohoo",
"Wooo",
"Wordmark",
"wordprocessingml",
"worklet",
Expand Down Expand Up @@ -791,21 +805,7 @@
"zoneinfo",
"zxcv",
"zxldvw",
"inputmethod",
"copyable",
"مثال",
"moveElemsAttrsToGroup",
"removeHiddenElems",
"moveGroupAttrsToElems",
"Dtype",
"Areport",
"mple",
"Selec",
"setuptools",
"DYNAMICEXTERNAL",
"RNCORE",
"Wooo",
"Splittable"
"مثال"
],
"ignorePaths": [
"src/languages/de.ts",
Expand Down
5 changes: 4 additions & 1 deletion src/components/EmojiWithTooltip/index.ios.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ import Text from '@components/Text';
import useThemeStyles from '@hooks/useThemeStyles';
import type EmojiWithTooltipProps from './types';

function EmojiWithTooltip({emojiCode, style = {}, isMedium = false}: EmojiWithTooltipProps) {
function EmojiWithTooltip({emojiCode, style = {}, isMedium = false, isOnSeparateLine = false}: EmojiWithTooltipProps) {
const styles = useThemeStyles();
const isCustomEmoji = emojiCode === '\uE100';
if (isOnSeparateLine) {
return <Text style={[styles.emojisWithTextFontSizeAligned, isCustomEmoji && styles.customEmojiFontAlignment]}>{emojiCode}</Text>;
}

return isMedium ? (
<Text style={style}>
Expand Down
1 change: 1 addition & 0 deletions src/components/EmojiWithTooltip/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ type EmojiWithTooltipProps = {
emojiCode: string;
style?: StyleProp<TextStyle>;
isMedium?: boolean;
isOnSeparateLine?: boolean;
};

export default EmojiWithTooltipProps;
20 changes: 10 additions & 10 deletions src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import React, {useMemo} from 'react';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@paulnjs You shouldn't remove import React here. It will cause a crash app

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@truph01 Updated

import React from 'react';
import type {TextStyle} from 'react-native';
import type {CustomRendererProps, TPhrasing, TText} from 'react-native-render-html';
import EmojiWithTooltip from '@components/EmojiWithTooltip';
import useThemeStyles from '@hooks/useThemeStyles';

function EmojiRenderer({tnode, style: styleProp}: CustomRendererProps<TText | TPhrasing>) {
const styles = useThemeStyles();
const style = useMemo(() => {
if ('islarge' in tnode.attributes) {
return [styleProp as TextStyle, styles.onlyEmojisText];
}

if ('ismedium' in tnode.attributes) {
return [styleProp as TextStyle, styles.emojisWithTextFontSize, styles.verticalAlignTopText];
}
let style;
if ('islarge' in tnode.attributes) {
style = [styleProp as TextStyle, styles.onlyEmojisText];
} else if ('ismedium' in tnode.attributes) {
style = [styleProp as TextStyle, styles.emojisWithTextFontSize, styles.verticalAlignTopText];
} else {
style = null;
}
Comment on lines +11 to +17

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
if ('islarge' in tnode.attributes) {
style = [styleProp as TextStyle, styles.onlyEmojisText];
}
if ('ismedium' in tnode.attributes) {
style = [styleProp as TextStyle, styles.emojisWithTextFontSize, styles.verticalAlignTopText];
}
if ('islarge' in tnode.attributes) {
style = [styleProp as TextStyle, styles.onlyEmojisText];
} else if ('ismedium' in tnode.attributes) {
style = [styleProp as TextStyle, styles.emojisWithTextFontSize, styles.verticalAlignTopText];
} else {
style = null;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@truph01 Updated


return null;
}, [tnode.attributes, styles, styleProp]);
return (
<EmojiWithTooltip
isOnSeparateLine={'oneline' in tnode.attributes}
style={[style, styles.cursorDefault, styles.emojiDefaultStyles]}
emojiCode={'data' in tnode ? tnode.data : ''}
isMedium={'ismedium' in tnode.attributes}
Expand Down
6 changes: 6 additions & 0 deletions src/libs/EmojiUtils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,11 @@ function getZWNJCursorOffset(text: string, cursorPosition: number | undefined |
return textWithZWNJBeforeCursor.length - textBeforeCursor.length;
}

function isEmojiOnSeparateLine(line: string) {
const trimmed = line.replaceAll(/<br\s*\/?>/gi, '').trim();
return /^<emoji>.*<\/emoji>$/.test(trimmed);
}

export type {HeaderIndices, EmojiPickerList, EmojiPickerListItem};

export {
Expand Down Expand Up @@ -725,4 +730,5 @@ export {
processFrequentlyUsedEmojis,
insertZWNJBetweenDigitAndEmoji,
getZWNJCursorOffset,
isEmojiOnSeparateLine,
};
20 changes: 14 additions & 6 deletions src/pages/home/report/comment/TextCommentFragment.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Str} from 'expensify-common';
import isEmpty from 'lodash/isEmpty';
import React, {memo, useEffect, useMemo} from 'react';
import React, {useEffect} from 'react';
import type {StyleProp, TextStyle} from 'react-native';
import Text from '@components/Text';
import ZeroWidthView from '@components/ZeroWidthView';
Expand All @@ -11,7 +11,7 @@ import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import convertToLTR from '@libs/convertToLTR';
import {canUseTouchScreen} from '@libs/DeviceCapabilities';
import {containsOnlyCustomEmoji as containsOnlyCustomEmojiUtil, containsOnlyEmojis as containsOnlyEmojisUtil, splitTextWithEmojis} from '@libs/EmojiUtils';
import {containsOnlyCustomEmoji as containsOnlyCustomEmojiUtil, containsOnlyEmojis as containsOnlyEmojisUtil, isEmojiOnSeparateLine, splitTextWithEmojis} from '@libs/EmojiUtils';
import Parser from '@libs/Parser';
import Performance from '@libs/Performance';
import {getHtmlWithAttachmentID, getTextFromHtml} from '@libs/ReportActionsUtils';
Expand Down Expand Up @@ -62,7 +62,7 @@ function TextCommentFragment({fragment, styleAsDeleted, reportActionID, styleAsM

const message = isEmpty(iouMessage) ? text : iouMessage;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

❌ PERF-4 (docs)

Removing useMemo causes processedTextArray to be recalculated on every render, even when message hasn't changed. This creates unnecessary computation overhead.

Fix: Restore the useMemo hook:

const processedTextArray = useMemo(() => splitTextWithEmojis(message), [message]);

const processedTextArray = useMemo(() => splitTextWithEmojis(message), [message]);
const processedTextArray = splitTextWithEmojis(message);

useEffect(() => {
Performance.markEnd(CONST.TIMING.SEND_MESSAGE, {message: text});
Expand All @@ -74,7 +74,7 @@ function TextCommentFragment({fragment, styleAsDeleted, reportActionID, styleAsM
// on native, we render it as text, not as html
// on other device, only render it as text if the only difference is <br /> tag
const containsOnlyEmojis = containsOnlyEmojisUtil(text ?? '');
const containsOnlyCustomEmoji = useMemo(() => containsOnlyCustomEmojiUtil(text), [text]);
const containsOnlyCustomEmoji = containsOnlyCustomEmojiUtil(text);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

❌ PERF-4 (docs)

Removing useMemo causes containsOnlyCustomEmoji to be recalculated on every render, even when text hasn't changed. The containsOnlyCustomEmojiUtil function performs string processing that should be memoized.

Fix: Restore the useMemo hook:

const containsOnlyCustomEmoji = useMemo(() => containsOnlyCustomEmojiUtil(text), [text]);

const containsEmojis = CONST.REGEX.ALL_EMOJIS.test(text ?? '');
if (!shouldRenderAsText(html, text ?? '') && !(containsOnlyEmojis && styleAsDeleted)) {
const editedTag = fragment?.isEdited ? `<edited ${styleAsDeleted ? 'deleted' : ''}></edited>` : '';
Expand All @@ -90,7 +90,15 @@ function TextCommentFragment({fragment, styleAsDeleted, reportActionID, styleAsM
if (!htmlContent.includes('<emoji>')) {
htmlContent = Parser.replace(htmlContent, {filterRules: ['emoji'], shouldEscapeText: false});
}
htmlContent = Str.replaceAll(htmlContent, '<emoji>', '<emoji ismedium>');
const lines = htmlContent.split(/<br\s*\/?>/i);
const processedLines = lines.map((line) => {
if (isEmojiOnSeparateLine(line)) {
return line.replace('<emoji>', '<emoji ismedium oneline >');
}
return line.replace('<emoji>', '<emoji ismedium>');
});

htmlContent = processedLines.join('<br />');
}

let htmlWithTag = editedTag ? `${htmlContent}${editedTag}` : htmlContent;
Expand Down Expand Up @@ -158,4 +166,4 @@ function TextCommentFragment({fragment, styleAsDeleted, reportActionID, styleAsM
);
}

export default memo(TextCommentFragment);
export default TextCommentFragment;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

❌ PERF-4 (docs)

Removing memo wrapper from TextCommentFragment component can cause unnecessary re-renders when parent components update, even if this component's props haven't changed. This is particularly important for components used in lists or frequently re-rendered contexts.

Fix: Restore the memo wrapper:

export default memo(TextCommentFragment);

Comment thread
paulnjs marked this conversation as resolved.
Loading
Loading