Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
a20414b
Migrate first batch of components
roryabraham Jan 14, 2026
2f3e8cf
Migrate first batch of hooks
roryabraham Jan 14, 2026
e5e3243
Migrate AddUnreportedExpense
roryabraham Jan 14, 2026
9badcd3
Migrate first batch of pages
roryabraham Jan 14, 2026
b088020
Merge main into Rory-ReactCompilerFirstPass
roryabraham Jan 14, 2026
64b9b30
Fix implicit any in useSelectedTransactionsActions
roryabraham Jan 14, 2026
8973d66
Fix errors in useSearchHighlightAndScroll
roryabraham Jan 14, 2026
f20b4b6
Revert files that don't compile or have conflicting ESLint rules
roryabraham Jan 14, 2026
9078e51
Merge main into Rory-ReactCompilerFirstPass
roryabraham Jan 14, 2026
1dd5b4b
Fix lint
roryabraham Jan 14, 2026
ad5bfe9
Merge main into Rory-ReactCompilerFirstPass
roryabraham Jan 14, 2026
8c3de4e
Revert AddUnreportedExpense (useOnyx selector incompatibility)
roryabraham Jan 14, 2026
06e9f3c
Update warning count
roryabraham Jan 14, 2026
4f3412a
Merge main into Rory-ReactCompilerFirstPass
roryabraham Jan 15, 2026
b6bdb2b
Fix lint after merge
roryabraham Jan 15, 2026
04182d0
Fix lint
roryabraham Jan 16, 2026
210de0e
Merge main into Rory-ReactCompilerFirstPass
roryabraham Jan 16, 2026
4ed50a4
Merge main into Rory-ReactCompilerFirstPass
roryabraham Jan 16, 2026
f293678
Fix type error in IOUTest
roryabraham Jan 16, 2026
ac8e5b9
Adjust warning count after merge to main
roryabraham Jan 16, 2026
9e9f703
Merge main into Rory-ReactCompilerFirstPass
roryabraham Jan 19, 2026
9493723
Revert unintentional logic change in TransactionListItem
roryabraham Jan 19, 2026
9876251
fix EmptySearchView after merge
roryabraham Jan 19, 2026
0bf7ad2
Update warning count after merging main
roryabraham Jan 19, 2026
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"test:debug": "TZ=utc NODE_OPTIONS='--inspect-brk --experimental-vm-modules' jest --runInBand",
"perf-test": "NODE_OPTIONS=--experimental-vm-modules npx reassure",
"typecheck": "NODE_OPTIONS=--max_old_space_size=8192 tsc",
"lint": "NODE_OPTIONS=--max_old_space_size=8192 eslint . --max-warnings=668 --cache --cache-location=node_modules/.cache/eslint --cache-strategy content --concurrency=auto",
"lint": "NODE_OPTIONS=--max_old_space_size=8192 eslint . --max-warnings=378 --cache --cache-location=node_modules/.cache/eslint --cache-strategy content --concurrency=auto",
"lint-changed": "NODE_OPTIONS=--max_old_space_size=8192 ./scripts/lintChanged.sh",
"check-lazy-loading": "ts-node scripts/checkLazyLoading.ts",
"lint-watch": "npx eslint-watch --watch --changed",
Expand Down
131 changes: 48 additions & 83 deletions src/components/Form/FormWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, {useCallback, useMemo, useRef} from 'react';
import React, {useRef} from 'react';
import type {RefObject} from 'react';
// eslint-disable-next-line no-restricted-imports
import type {ScrollView as RNScrollView, StyleProp, ViewStyle} from 'react-native';
Expand Down Expand Up @@ -103,9 +103,9 @@ function FormWrapper({

const [formState] = useOnyx<OnyxFormKey, Form>(`${formID}`, {canBeMissing: true});

const errorMessage = useMemo(() => (formState ? getLatestErrorMessage(formState) : undefined), [formState]);
const errorMessage = formState ? getLatestErrorMessage(formState) : undefined;

const onFixTheErrorsLinkPressed = useCallback(() => {
const onFixTheErrorsLinkPressed = () => {
const errorFields = !isEmptyObject(errors) ? errors : (formState?.errorFields ?? {});
const focusKey = Object.keys(inputRefs.current ?? {}).find((key) => key in errorFields);

Expand Down Expand Up @@ -134,7 +134,7 @@ function FormWrapper({

// Focus the input after scrolling, as on the Web it gives a slightly better visual result
focusInput?.focus?.();
}, [errors, formState?.errorFields, inputRefs]);
};

// If either of `addBottomSafeAreaPadding` or `shouldSubmitButtonStickToBottom` is explicitly set,
// we expect that the user wants to use the new edge-to-edge mode.
Expand All @@ -159,88 +159,53 @@ function FormWrapper({
style: submitButtonStyles,
});

const SubmitButton = useMemo(
() =>
isSubmitButtonVisible && (
<FormAlertWithSubmitButton
buttonText={submitButtonText}
isDisabled={isSubmitDisabled}
isAlertVisible={((!isEmptyObject(errors) || !isEmptyObject(formState?.errorFields)) && !shouldHideFixErrorsAlert) || !!errorMessage}
isLoading={!!formState?.isLoading || isLoading}
message={isEmptyObject(formState?.errorFields) ? errorMessage : undefined}
onSubmit={onSubmit}
footerContent={footerContent}
onFixTheErrorsLinkPressed={onFixTheErrorsLinkPressed}
containerStyles={[
styles.mh0,
styles.mt5,
submitFlexEnabled && styles.flex1,
submitButtonStylesWithBottomSafeAreaPadding,
shouldSubmitButtonStickToBottom && [styles.stickToBottom, style],
]}
enabledWhenOffline={enabledWhenOffline}
isSubmitActionDangerous={isSubmitActionDangerous}
disablePressOnEnter={disablePressOnEnter}
enterKeyEventListenerPriority={enterKeyEventListenerPriority}
shouldRenderFooterAboveSubmit={shouldRenderFooterAboveSubmit}
shouldBlendOpacity={shouldSubmitButtonBlendOpacity}
shouldPreventDefaultFocusOnPress={shouldPreventDefaultFocusOnPressSubmit}
/>
),
[
disablePressOnEnter,
enterKeyEventListenerPriority,
enabledWhenOffline,
errorMessage,
errors,
footerContent,
formState?.errorFields,
formState?.isLoading,
isLoading,
isSubmitActionDangerous,
isSubmitButtonVisible,
isSubmitDisabled,
onFixTheErrorsLinkPressed,
onSubmit,
shouldHideFixErrorsAlert,
shouldSubmitButtonBlendOpacity,
shouldSubmitButtonStickToBottom,
style,
styles.flex1,
styles.mh0,
styles.mt5,
styles.stickToBottom,
submitButtonStylesWithBottomSafeAreaPadding,
submitButtonText,
submitFlexEnabled,
shouldRenderFooterAboveSubmit,
shouldPreventDefaultFocusOnPressSubmit,
],
const SubmitButton = isSubmitButtonVisible && (
<FormAlertWithSubmitButton
buttonText={submitButtonText}
isDisabled={isSubmitDisabled}
isAlertVisible={((!isEmptyObject(errors) || !isEmptyObject(formState?.errorFields)) && !shouldHideFixErrorsAlert) || !!errorMessage}
isLoading={!!formState?.isLoading || isLoading}
message={isEmptyObject(formState?.errorFields) ? errorMessage : undefined}
onSubmit={onSubmit}
footerContent={footerContent}
onFixTheErrorsLinkPressed={onFixTheErrorsLinkPressed}
containerStyles={[
Comment thread
roryabraham marked this conversation as resolved.
styles.mh0,
styles.mt5,
submitFlexEnabled && styles.flex1,
submitButtonStylesWithBottomSafeAreaPadding,
shouldSubmitButtonStickToBottom && [styles.stickToBottom, style],
]}
enabledWhenOffline={enabledWhenOffline}
isSubmitActionDangerous={isSubmitActionDangerous}
disablePressOnEnter={disablePressOnEnter}
enterKeyEventListenerPriority={enterKeyEventListenerPriority}
shouldRenderFooterAboveSubmit={shouldRenderFooterAboveSubmit}
shouldBlendOpacity={shouldSubmitButtonBlendOpacity}
shouldPreventDefaultFocusOnPress={shouldPreventDefaultFocusOnPressSubmit}
/>
);

const scrollViewContent = useCallback(
() => (
<FormElement
key={formID}
ref={formContentRef}
style={[style, styles.pb5]}
onLayout={() => {
if (!shouldScrollToEnd) {
return;
}
// eslint-disable-next-line @typescript-eslint/no-deprecated
InteractionManager.runAfterInteractions(() => {
requestAnimationFrame(() => {
formRef.current?.scrollToEnd({animated: true});
});
const scrollViewContent = () => (
<FormElement
key={formID}
ref={formContentRef}
style={[style, styles.pb5]}
onLayout={() => {
if (!shouldScrollToEnd) {
return;
}
// eslint-disable-next-line @typescript-eslint/no-deprecated
InteractionManager.runAfterInteractions(() => {
requestAnimationFrame(() => {
formRef.current?.scrollToEnd({animated: true});
});
}}
>
{children}
{!shouldSubmitButtonStickToBottom && SubmitButton}
</FormElement>
),
[formID, style, styles.pb5, children, shouldSubmitButtonStickToBottom, SubmitButton, shouldScrollToEnd],
});
}}
>
{children}
{!shouldSubmitButtonStickToBottom && SubmitButton}
</FormElement>
);

if (!shouldUseScrollView) {
Expand Down
32 changes: 12 additions & 20 deletions src/components/InvertedFlatList/index.e2e.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, {useMemo} from 'react';
import React from 'react';
import type {ScrollViewProps, ViewToken} from 'react-native';
import {DeviceEventEmitter, FlatList} from 'react-native';
import type {ReportAction} from '@src/types/onyx';
Expand All @@ -9,33 +9,25 @@ const AUTOSCROLL_TO_TOP_THRESHOLD = 128;
function InvertedFlatListE2E({ref, ...props}: InvertedFlatListProps<ReportAction>) {
const {shouldEnableAutoScrollToTopThreshold, ...rest} = props;

const handleViewableItemsChanged = useMemo(
() =>
({viewableItems}: {viewableItems: ViewToken[]}) => {
DeviceEventEmitter.emit('onViewableItemsChanged', viewableItems);
},
[],
);

const maintainVisibleContentPosition = useMemo(() => {
const config: ScrollViewProps['maintainVisibleContentPosition'] = {
// This needs to be 1 to avoid using loading views as anchors.
minIndexForVisible: rest.data?.length ? Math.min(1, rest.data.length - 1) : 0,
};
const handleViewableItemsChanged = ({viewableItems}: {viewableItems: ViewToken[]}) => {
DeviceEventEmitter.emit('onViewableItemsChanged', viewableItems);
};

if (shouldEnableAutoScrollToTopThreshold) {
config.autoscrollToTopThreshold = AUTOSCROLL_TO_TOP_THRESHOLD;
}
const config: ScrollViewProps['maintainVisibleContentPosition'] = {
// This needs to be 1 to avoid using loading views as anchors.
minIndexForVisible: rest.data?.length ? Math.min(1, rest.data.length - 1) : 0,
};

return config;
}, [shouldEnableAutoScrollToTopThreshold, rest.data?.length]);
if (shouldEnableAutoScrollToTopThreshold) {
config.autoscrollToTopThreshold = AUTOSCROLL_TO_TOP_THRESHOLD;
}

return (
<FlatList<ReportAction>
// eslint-disable-next-line react/jsx-props-no-spreading
{...rest}
ref={ref}
maintainVisibleContentPosition={maintainVisibleContentPosition}
maintainVisibleContentPosition={config}
inverted
onViewableItemsChanged={handleViewableItemsChanged}
/>
Expand Down
19 changes: 8 additions & 11 deletions src/components/Navigation/SearchSidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type {ParamListBase} from '@react-navigation/native';
import React, {useEffect, useMemo} from 'react';
import React, {useEffect} from 'react';
import {View} from 'react-native';
import {useSearchContext} from '@components/Search/SearchContext';
import useLocalize from '@hooks/useLocalize';
Expand Down Expand Up @@ -29,23 +29,20 @@ function SearchSidebar({state}: SearchSidebarProps) {
const params = route?.params as SearchFullscreenNavigatorParamList[typeof SCREENS.SEARCH.ROOT] | undefined;
const {lastSearchType, setLastSearchType, currentSearchResults} = useSearchContext();

const queryJSON = useMemo(() => {
if (!params?.q) {
return undefined;
}
const queryJSON = params?.q ? buildSearchQueryJSON(params.q, params.rawQuery) : undefined;

return buildSearchQueryJSON(params.q, params.rawQuery);
}, [params?.q, params?.rawQuery]);
const searchType = currentSearchResults?.search?.type;
const isSearchLoading = currentSearchResults?.search?.isLoading;

useEffect(() => {
if (!currentSearchResults?.search?.type) {
if (!searchType) {
return;
}

setLastSearchType(currentSearchResults.search.type);
}, [lastSearchType, queryJSON, setLastSearchType, currentSearchResults?.search?.type]);
setLastSearchType(searchType);
}, [lastSearchType, queryJSON, setLastSearchType, searchType]);

const shouldShowLoadingState = route?.name === SCREENS.RIGHT_MODAL.SEARCH_MONEY_REQUEST_REPORT ? false : !isOffline && !!currentSearchResults?.search?.isLoading;
const shouldShowLoadingState = route?.name === SCREENS.RIGHT_MODAL.SEARCH_MONEY_REQUEST_REPORT ? false : !isOffline && !!isSearchLoading;

if (shouldUseNarrowLayout) {
return null;
Expand Down
47 changes: 15 additions & 32 deletions src/components/ReportWelcomeText.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {createPersonalDetailsSelector} from '@selectors/PersonalDetails';
import React, {useMemo} from 'react';
import React from 'react';
import {View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import useEnvironment from '@hooks/useEnvironment';
Expand Down Expand Up @@ -90,37 +90,20 @@ function ReportWelcomeText({report, policy}: ReportWelcomeTextProps) {
moneyRequestOptions.includes(CONST.IOU.TYPE.TRACK) ||
moneyRequestOptions.includes(CONST.IOU.TYPE.SPLIT);

const reportDetailsLink = useMemo(() => {
if (!report?.reportID) {
return '';
}

return `${environmentURL}/${ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report.reportID, Navigation.getReportRHPActiveRoute())}`;
}, [environmentURL, report?.reportID]);

const welcomeHeroText = useMemo(() => {
if (isInvoiceRoom) {
return translate('reportActionsView.sayHello');
}

if (isChatRoom) {
return translate('reportActionsView.welcomeToRoom', {roomName: reportName});
}

if (isSelfDM) {
return translate('reportActionsView.yourSpace');
}

if (isSystemChat) {
return reportName;
}

if (isPolicyExpenseChat) {
return translate('reportActionsView.welcomeToRoom', {roomName: policyName});
}

return translate('reportActionsView.sayHello');
}, [isChatRoom, isInvoiceRoom, isPolicyExpenseChat, isSelfDM, isSystemChat, translate, policyName, reportName]);
const reportDetailsLink = report?.reportID ? `${environmentURL}/${ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report.reportID, Navigation.getReportRHPActiveRoute())}` : '';

let welcomeHeroText = translate('reportActionsView.sayHello');
if (isInvoiceRoom) {
welcomeHeroText = translate('reportActionsView.sayHello');
} else if (isChatRoom) {
welcomeHeroText = translate('reportActionsView.welcomeToRoom', {roomName: reportName});
} else if (isSelfDM) {
welcomeHeroText = translate('reportActionsView.yourSpace');
} else if (isSystemChat) {
welcomeHeroText = reportName;
} else if (isPolicyExpenseChat) {
welcomeHeroText = translate('reportActionsView.welcomeToRoom', {roomName: policyName});
}

// If we are the only participant (e.g. solo group chat) then keep the current user personal details so the welcome message does not show up empty.
const shouldExcludeCurrentUser = participantAccountIDs.length > 0;
Expand Down
Loading
Loading