From 9003a0a04b3827d5224bb14b6e94fdaceb375baa Mon Sep 17 00:00:00 2001 From: "Situ Chandra Shil (via MelvinBot)" Date: Wed, 14 Oct 2026 22:59:52 +0000 Subject: [PATCH] Show a spinner below the DynamicForm header and drop redundant shouldUseGoBackButton props Co-authored-by: Situ Chandra Shil --- src/components/DynamicForm/index.tsx | 9 +++++++-- src/components/ReportOrNotFoundGuard.tsx | 2 +- .../connections/ConnectionsMergePageBase.tsx | 5 +---- .../ConnectionsReceiptPartnersPage.tsx | 5 +---- .../DynamicForm/DynamicFormFlowTest.tsx | 16 ++++++++++++++++ 5 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/components/DynamicForm/index.tsx b/src/components/DynamicForm/index.tsx index db49583655f3..f068ed91681f 100644 --- a/src/components/DynamicForm/index.tsx +++ b/src/components/DynamicForm/index.tsx @@ -1,8 +1,9 @@ -import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; +import ActivityIndicator from '@components/ActivityIndicator'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useSubPage from '@hooks/useSubPage'; +import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import {findLastPageIndex} from '@libs/SubPageUtils'; @@ -15,6 +16,7 @@ import type {DynamicFormPage} from '@src/types/onyx'; import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; import React, {useEffect, useRef, useState} from 'react'; +import {View} from 'react-native'; import type {DynamicFormFlowProps, DynamicFormSubPageProps, DynamicFormValues} from './types'; @@ -55,6 +57,7 @@ function DynamicFormFlow({ layout = 'auto', hasConfirmation = true, }: DynamicFormFlowProps) { + const styles = useThemeStyles(); const {translate} = useLocalize(); const [draft, draftMetadata] = useOnyx(`${formID}Draft`); const [sensitiveAnswersByForm] = useOnyx(ONYXKEYS.RAM_ONLY_DYNAMIC_FORM_SENSITIVE_ANSWERS); @@ -220,7 +223,9 @@ function DynamicFormFlow({ shouldShowStepIndicator={isStepIndicatorAsked && stepNames.length > 1} > {isDraftLoading || isRedirecting || isOnUnavailablePage || (!hasPageToShow && pages.length > 0) ? ( - + + + ) : ( ; + return ; } if (decision === 'notFound') { diff --git a/src/pages/workspace/connections/ConnectionsMergePageBase.tsx b/src/pages/workspace/connections/ConnectionsMergePageBase.tsx index fb8c65ec2a9e..6d9d785f3bb2 100644 --- a/src/pages/workspace/connections/ConnectionsMergePageBase.tsx +++ b/src/pages/workspace/connections/ConnectionsMergePageBase.tsx @@ -152,10 +152,7 @@ function ConnectionsMergePageBase({policyID, category, cards, shouldBeBlocked}: fullPageNotFoundViewProps={{onBackButtonPress: () => Navigation.goBack(ROUTES.WORKSPACE_CONNECTIONS.getRoute(policyID))}} > {isFetchingConnections ? ( - + ) : ( Navigation.goBack(ROUTES.WORKSPACE_CONNECTIONS.getRoute(policyID))}} > {isLoading ? ( - + ) : ( ({ })); jest.mock('@libs/Navigation/Navigation', () => ({navigate: jest.fn(), goBack: jest.fn()})); jest.mock('@components/ScreenWrapper', () => jest.fn(({children}: {children: React.ReactNode}) => children)); +jest.mock('@components/ActivityIndicator', () => jest.fn(() => null)); jest.mock('@components/FullscreenLoadingIndicator', () => jest.fn(() => null)); jest.mock('@components/InteractiveStepWrapper', () => jest.fn((props: {stepNames: string[]; startStepIndex: number}) => mockStepWrapper(props))); jest.mock('@components/Header/composed/HeaderWithBackButtonAndTitle', () => @@ -418,6 +422,18 @@ describe('DynamicFormFlow', () => { expect(subPageOptions.skipPages).toEqual([]); }); + it('shows a spinner below the header while it loads, so no second Go Back button covers the header', async () => { + // Given a flow that is redirecting to another page + // When the flow renders + await renderFlow({isRedirecting: true}); + + // Then the header is drawn with a plain spinner under it instead of a page + expect(HeaderWithBackButtonAndTitle).toHaveBeenCalled(); + expect(ActivityIndicator).toHaveBeenCalled(); + expect(FullscreenLoadingIndicator).not.toHaveBeenCalled(); + expect(mockCurrentPage).not.toHaveBeenCalled(); + }); + it('sends the user back to the incomplete page without pushing a second copy of it', async () => { // Given a form whose SSN was lost when the app restarted const {subPageOptions, onSubmit} = await renderFlow({draft: {firstName: 'Jane', legalType: 'PRIVATE'}});