[HOLD] [Odometer] Fix state restoration after App reload when image preview is open#82407
Conversation
Codecov Report❌ Looks like you've decreased code coverage for some files. Please write tests to increase, or at least maintain, the existing level of code coverage. See our documentation here for how to interpret this table.
|
|
@DylanDylann Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button] |
|
@Julesssss Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button] |
|
Missclick - it's not ready for review yet |
| if (isEditingConfirmation) { | ||
| return; | ||
| } | ||
| if (!endImageSource?.startsWith('blob:')) { |
There was a problem hiding this comment.
❌ PERF-12 (docs)
Missing cleanup for blob validation fetch
The validateOdometerBlobSource function initiates a fetch request but doesn't provide a cleanup mechanism. If the component unmounts while the fetch is in progress, the promise continues executing and may attempt to call removeMoneyRequestOdometerImage after unmount, potentially causing memory leaks or state updates on unmounted components.
Suggested fix:
const validateOdometerBlobSource = useCallback(
(imageSource: string | undefined, imageType: OdometerImageType) => {
if (\!imageSource?.startsWith('blob:')) {
return;
}
const controller = new AbortController();
fetch(imageSource, { signal: controller.signal })
.catch((error) => {
if (error.name === 'AbortError') {
return;
}
if (imageType === CONST.IOU.ODOMETER_IMAGE_TYPE.START) {
initialStartImageRef.current = undefined;
} else {
initialEndImageRef.current = undefined;
}
removeMoneyRequestOdometerImage(transactionID, imageType, isTransactionDraft);
});
return controller;
},
[isTransactionDraft, transactionID],
);
useEffect(() => {
const controller = validateOdometerBlobSource(startImageSource, CONST.IOU.ODOMETER_IMAGE_TYPE.START);
return () => controller?.abort();
}, [startImageSource, validateOdometerBlobSource]);
useEffect(() => {
const controller = validateOdometerBlobSource(endImageSource, CONST.IOU.ODOMETER_IMAGE_TYPE.END);
return () => controller?.abort();
}, [endImageSource, validateOdometerBlobSource]);Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.
| normalizedPath = '/'; | ||
| } | ||
|
|
||
| if (normalizedPath.includes('/receipt/')) { |
There was a problem hiding this comment.
❌ PERF-2 (docs)
Expensive work performed before validation
The code calls getStateFromPath(normalizedPath, options) before validating whether focusedRouteAsPartial meets all required conditions. If the route name check or parameter validation fails, the expensive state parsing was unnecessary.
Suggested fix:
Move the validation logic before calling getStateFromPath. Since you need the state to get the focused route, consider restructuring to avoid the second getStateFromPath call:
// First check if we need special handling
if (normalizedPath.includes('/receipt/')) {
const stateWithOdometerReceiptPreview = getStateFromPath(normalizedPath, options) as PartialState<NavigationState<RootNavigatorParamList>>;
const focusedRoute = stateWithOdometerReceiptPreview ? findFocusedRoute(stateWithOdometerReceiptPreview) : undefined;
const focusedRouteAsPartial = focusedRoute as NavigationPartialRoute | undefined;
if (focusedRouteAsPartial?.name === SCREENS.MONEY_REQUEST.RECEIPT_PREVIEW && isRouteWithOdometerReceiptPreviewParams(focusedRouteAsPartial)) {
const {reportID} = focusedRouteAsPartial.params;
if (isValidReportID(reportID)) {
defaultFullScreenRouteSource = focusedRouteAsPartial;
const {action, iouType, transactionID} = focusedRouteAsPartial.params;
normalizedPath = `/${ROUTES.DISTANCE_REQUEST_CREATE_TAB_ODOMETER.getRoute(action, iouType, transactionID, reportID)}`;
}
}
}
const state = getStateFromPath(normalizedPath, options) as PartialState<NavigationState<RootNavigatorParamList>>;Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.
|
|
||
| const startImageSource = useMemo(() => getImageSource(odometerStartImage), [getImageSource, odometerStartImage]); | ||
| const endImageSource = useMemo(() => getImageSource(odometerEndImage), [getImageSource, odometerEndImage]); | ||
| const hasStartOdometerImage = !!startImageSource; |
There was a problem hiding this comment.
❌ PERF-6 (docs)
Redundant state derived from memo
hasStartOdometerImage and hasEndOdometerImage are stored as constants but could be computed inline where used, or these are already derivable from startImageSource and endImageSource which are memoized.
Suggested fix:
Since these are only used once each in the handleOdometerImagePress calls, compute them inline:
onPress={() => {
handleOdometerImagePress(CONST.IOU.ODOMETER_IMAGE_TYPE.START, !!startImageSource);
}}
// and
onPress={() => {
handleOdometerImagePress(CONST.IOU.ODOMETER_IMAGE_TYPE.END, !!endImageSource);
}}This eliminates the intermediate constants that don't add clarity.
Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6d134646b2
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const navigateBack = useCallback(() => { | ||
| Navigation.goBack(odometerRoute); | ||
| }, [odometerRoute]); | ||
| Navigation.goBack(); |
There was a problem hiding this comment.
Restore explicit back target for odometer image screen
Using Navigation.goBack() without a fallback breaks the odometer flow when this screen is opened from a fresh URL state (for example after browser refresh or direct deep link): there may be no odometer route in history, so back navigation returns to an unrelated root screen (or cannot go back) instead of the odometer step. The previous goBack(ROUTES.DISTANCE_REQUEST_CREATE_TAB_ODOMETER...) behavior guaranteed users returned to the form after selecting/canceling an image.
Useful? React with 👍 / 👎.
Explanation of Change
ON HOLD -> There are more issues related to this that have to be taken care of and this is not a priority so I will be working on this PR from time to time and fix multitude of small issues here
Fixed Issues
$ #77266
PROPOSAL: N/A
Tests
Offline tests
QA Steps
// TODO: These must be filled out, or the issue title must include "[No QA]."
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectioncanBeMissingparam foruseOnyxtoggleReportand notonIconClick)src/languages/*files and using the translation methodSTYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.ScrollViewcomponent to make it scrollable when more elements are added to the page.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari