Skip to content

[Due for payment 2026-11-10] [$175] Bank account-This field is required error in the company name field after completing Onfido #104049

Closed

Description

@applause-bot

If you haven’t already, check out our contributing guidelines for onboarding. To join our Slack channel, fill out this form.


Version Number: 9.5.9-0
Reproducible in staging?: Yes
Reproducible in production?: Unable to validate in Prod - Provide reasoning.
If this was caught during regression testing, add the test name, ID and link from BrowserStack: https://test-management.browserstack.com/tc/TC-3413
Email or phone of affected tester (no customers): N/A
Issue reported by: Applause Internal Team
Bug source: Exploratory - Significant User Experience Deterioration
Device used: iPhone 15 iOS 26.6.1
App Component: Other

Action Performed:

  1. Sign in to ND
  2. Create a workspace, go to Workflows/ Payments
  3. Tap Add bank account - Log into your bank - Regions bank - Saving account 1111
  4. Set the First Name: First, Last Name: Last
  5. Complete the personal details step
  6. Tap Continue and complete the Onfido verification process, tap Submit video
  7. Observe the company name field after being navigated to the Company info step

Expected Result:

No errors appear on the Company info page

Actual Result:

"This field is required" error is shown in the company name field after completing Onfido verification flow

Workaround:

Unknown

Platforms:

  • Android: App
  • Android: mWeb Chrome
  • iOS: App
  • iOS: mWeb Safari
  • iOS: mWeb Chrome
  • Windows: Chrome
  • MacOS: Chrome Safari

Screenshots/Videos

_To

Bug7286547_1792000548522.company_name_error.mp4

be added by Applause Leads_

View all open jobs on GitHub

Applause Internal Information
Exported by: Chandana Kiran
Bug ID: 7286547

Upwork Automation - Do Not Edit
Issue OwnerCurrent Issue Owner: @mallenexpensify

Activity

  1. added
    BugSomething is broken. Auto assigns a BugZero manager.
    on Oct 14, 2026
  2. applause-bot commented on Oct 14, 2026

    @applause-bot
    Author

    While Applause doesn't embed attachments for this bug, here are the links for quick view:
    https://platform.applause.com/services/links/v1/external/89557d08b0f0aff5880c5e7ae3bb4d7811bd369d95d0694f30d77edab0dfda3b

  3. MelvinBot commented on Oct 14, 2026

    @MelvinBot
    Contributor

    Proposal

    What is the root cause of that problem?

    The Company name input loses focus without any user action, and that blur triggers the form's "required" check while the field is still empty.

    1. After Onfido succeeds, handleOnfidoSuccess navigates to the Company step right away (VerifyIdentity.tsx:53-60). On iOS, the full-screen native Onfido screen is still closing at that moment.
    2. The Company name step uses shouldDelayAutoFocus (NameBusiness.tsx:70). That calls focus() after a fixed 300 ms timer (useDelayedAutoFocus.ts:20-22). The timer does not wait for the transition or the native screen to finish closing.
    3. iOS then removes focus from the input, so onBlur fires. On native, Visibility.hasFocus() always returns true (Visibility/index.native.ts:9). Nothing filters out this blur, so FormProvider marks the field as touched and validates it (FormProvider.tsx:505-534).
    4. getFieldRequiredErrors returns "This field is required" for the empty companyName (NameBusiness.tsx:38).

    What changes do you think we should make in order to solve the problem?

    Make useDelayedAutoFocus focus the input only after the transition really ends, not on a fixed timer. Use TransitionTracker.runAfterTransitions (with waitForUpcomingTransition), then isWindowReadyToFocus(). This is the pattern useAutoFocusInput already uses (useAutoFocusInput.ts:72-77). Cancel the task handle when the screen loses focus.

    This fixes the bug for every SingleFieldStep that uses shouldDelayAutoFocus, not just this one.

    What alternative solutions did you explore? (Optional)

    • Delay onSubmit() in handleOnfidoSuccess until the native Onfido screen has closed. This is narrower, and it only fixes the Onfido path.
    • Skip blur validation in FormProvider for fields the user has never pressed or edited. This changes how every form behaves, so it carries more regression risk.
    Investigation details
    • The error comes from the client's own validation, not the server. verifyIdentityForBankAccount only updates REIMBURSEMENT_ACCOUNT (isLoading and errors). It never sets errorFields on the form.
    • A new FormProvider starts with no fields touched. So the error needs a blur or a submit on the Company form itself. That rules out stale state carried over from the Onfido step.
    • This is likely iOS-only. iOS shows Onfido as a full-screen native view controller, and closing it can remove focus from an input that gained focus while it was closing.
    • The same kind of bug was fixed before: Signer info - "This field is required" error is shown by default on job title screen. That fix added shouldDelayAutoFocus. A fixed 300 ms delay is not enough when a native modal is closing at the same time.
    • I found no single PR that caused this. The relevant files have had no logic changes in about 2 months.
    • I checked this in the code only. I did not reproduce it on an iOS device.

    Next Steps for Contributor+ team:
    To accept: @MelvinBot implement [this](https://github.com/Expensify/App/issues/104049) to create a draft PR.
    To refine: @MelvinBot <your feedback>
    To reject: Explain why you are rejecting Melvin's proposal.


    view run

  4. added
    ExternalAdded to denote the issue can be worked on by a contributor
    on Oct 14, 2026
  5. melvin-bot commented on Oct 14, 2026

    @melvin-bot

    Triggered auto assignment to Contributor-plus team member for initial proposal review - @Eskalifer1 (External)

  6. changed the title [-]Bank account-This field is required error in the company name field after completing Onfido[/-] [+][$175] Bank account-This field is required error in the company name field after completing Onfido[/+] on Oct 14, 2026
  7. melvin-bot commented on Oct 14, 2026

    @melvin-bot
  8. juni2003 commented on Oct 14, 2026

    @juni2003

    Contributor details
    Your Expensify account email: juni.xatti@gmail.com
    Upwork Profile Link: https://www.upwork.com/freelancers/~01867ae3203c9b7815?mp_source=share

    Proposal

    Please re-state the problem that we are trying to solve in this issue.
    After completing the Onfido verification step, the user is navigated to the Company Info screen, where the "Company name" field immediately shows a "This field is required" error before the user has interacted with it.

    What is the root cause of that problem?
    This is a state-leak issue, not a mount-time validation bug. The shared form wrapper is rehydrating stale field errors from the previous reimbursement-account step.

    1. When VerifyIdentity.tsx completes the Onfido flow, it updates the draft state and advances the user, but it fails to clear the shared ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM error state in Onyx.
    2. BusinessInfo.tsx mounts the next sub-step (NameBusiness.tsx).
    3. Inside FormProvider.tsx, the component reads formState.errorFields during input registration. Because the Onyx state was never wiped during the transition, the companyName field consumes the stale required-field error and renders it immediately on first paint.
      The validate callback in NameBusiness.tsx is correctly configured to fire only on change/blur/submit, meaning the error is strictly a residual Onyx artifact.

    What changes do you think we should make in order to solve the problem?
    We need to explicitly clear the shared form errors when transitioning out of the Onfido step so the Business Info step mounts with a clean validation slate.

    In src/pages/ReimbursementAccount/USD/Requestor/VerifyIdentity/VerifyIdentity.tsx, right before or immediately after we call updateReimbursementAccountDraft({isOnfidoSetupComplete: true}); and trigger the step advancement, we should dispatch a clear action to Onyx:

    JavaScript

    import * as FormActions from '@libs/actions/FormActions';
    import ONYXKEYS from '@src/ONYXKEYS';
    
    // Inside the Onfido success callback / onSubmit:
    FormActions.clearErrors(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM);
    FormActions.clearErrorFields(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM);
    

    Alternatively, if we want a more resilient screen-level guard, we can add a mount useEffect in BusinessInfo.tsx that calls FormActions.clearErrorFields(...) on initial load. However, wiping it on the success transition in VerifyIdentity is cleaner.

    What alternative solutions did you explore? (Optional)
    A UI-only fix: adding a hasInteracted state flag locally inside NameBusiness.tsx to suppress errors until the user types. I rejected this because it masks the corrupted ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM state. The shared form state must be explicitly cleared to prevent this error from leaking into other sub-steps or blocking future submissions.

  9. melvin-bot commented on Oct 14, 2026

    @melvin-bot

    ✅ Contributor details stored successfully. Thank you for contributing to Expensify!

  10. melvin-bot commented on Oct 14, 2026

    @melvin-bot

    ✅ Contributor details stored successfully. Thank you for contributing to Expensify!

  11. janiya2 commented on Oct 14, 2026

    @janiya2

    Contributor details
    Your Expensify account email: janithad2@gmail.com
    Upwork Profile Link: https://www.upwork.com/freelancers/janithad

    Proposal

    Please re-state the problem that we are trying to solve in this issue.

    In the USD Verified Bank Account setup flow, after completing the Onfido identity verification step and submitting the video, the user is automatically navigated to the Company Info step (NameBusiness / Enter Business Name). Upon arriving at the screen without any user interaction or typing, the company name input immediately displays a red error: "This field is required".

    What is the root cause of that problem?

    The root cause is a race condition between native modal dismissal/navigation transition on iOS and the fixed setTimeout in useDelayedAutoFocus:

    1. When Onfido verification succeeds on native iOS, VerifyIdentity.tsx:53-60 immediately triggers onSubmit(). At this moment, the native Onfido SDK (@onfido/react-native-sdk) full-screen modal UIViewController is still animating closed.
    2. USDVerifiedBankAccountFlowPage navigates to NameBusiness, which renders SingleFieldStep.tsx with shouldDelayAutoFocus={true}.
    3. SingleFieldStep delegates autofocus to useDelayedAutoFocus.ts:20-22. Currently, useDelayedAutoFocus uses a blind, fixed timer:
      focusTimeoutRef.current = setTimeout(() => {
          ref.current?.focus();
      }, CONST.ANIMATED_TRANSITION);
      CONST.ANIMATED_TRANSITION is 300 ms.
    4. Because the iOS native modal dismissal and navigation transition typically take ~350–500 ms, ref.current?.focus() fires prematurely while the native view hierarchy is still resolving.
    5. As the native modal finish-dismisses, iOS resets the first responder hierarchy, causing an involuntary onBlur event on the TextInput.
    6. On native platforms, Visibility.hasFocus() unconditionally returns true. Therefore, in FormProvider.tsx:502-532, FormProvider treats this involuntary dismissal blur as a user-initiated blur:
      • setTouchedInput(inputID) marks companyName as touched.
      • onValidate(latestValues) runs validate() from NameBusiness.tsx:38.
      • Because companyName is still empty, getFieldRequiredErrors assigns errors.companyName = "This field is required".
    7. The user arrives on the screen and is immediately confronted with "This field is required".

    What changes do you think we should make in order to solve the problem?

    Refactor useDelayedAutoFocus in useDelayedAutoFocus.ts so that instead of using an arbitrary 300 ms timer, it waits for transitions to actually complete using navigation.addListener('transitionEnd'), TransitionTracker.runAfterTransitions, and isWindowReadyToFocus(), following the established autofocus architecture in useAutoFocusInput.ts.

    Targeted Implementation in src/hooks/useDelayedAutoFocus.ts:

    import type {RefObject} from 'react';
    import {useCallback} from 'react';
    import {useFocusEffect, useNavigation} from '@react-navigation/native';
    import TransitionTracker from '@libs/Navigation/TransitionTracker';
    import isWindowReadyToFocus from '@libs/isWindowReadyToFocus';
    import {shouldSkipAutoFocusDueToExistingFocus} from '@libs/NavigationFocusReturn';
    import {Priorities, resetCycle, tryClaim} from '@libs/ScreenFocusArbiter';
    import CONST from '@src/CONST';
    import useIsInLandscapeMode from './useIsInLandscapeMode';
    
    function useDelayedAutoFocus(ref: RefObject<{focus: () => void} | null>, shouldDelayAutoFocus: boolean) {
        const isInLandscapeMode = useIsInLandscapeMode();
        const navigation = useNavigation();
    
        useFocusEffect(
            useCallback(() => {
                if (!shouldDelayAutoFocus || isInLandscapeMode) {
                    return undefined;
                }
    
                let isCancelled = false;
                let focusTaskHandle: {cancel: () => void} | null = null;
                let timeoutId: NodeJS.Timeout | null = null;
    
                const triggerFocus = () => {
                    if (isCancelled) {
                        return;
                    }
                    focusTaskHandle = TransitionTracker.runAfterTransitions({
                        callback: () => {
                            isWindowReadyToFocus().then(() => {
                                if (isCancelled || !ref.current) {
                                    return;
                                }
                                if (shouldSkipAutoFocusDueToExistingFocus()) {
                                    return;
                                }
                                if (!tryClaim(Priorities.AUTO)) {
                                    return;
                                }
                                const beforeActive = typeof document !== 'undefined' ? document.activeElement : null;
                                ref.current.focus();
                                if (beforeActive !== null && document.activeElement === beforeActive) {
                                    resetCycle();
                                }
                            });
                        },
                        waitForUpcomingTransition: true,
                    });
                };
    
                const unsubscribe = navigation.addListener?.('transitionEnd', (event) => {
                    if (event?.data?.closing || isCancelled) {
                        return;
                    }
                    if (timeoutId) {
                        clearTimeout(timeoutId);
                        timeoutId = null;
                    }
                    triggerFocus();
                });
    
                // Fallback safety timeout if transitionEnd doesn't fire (e.g. non-animated navigation)
                timeoutId = setTimeout(() => {
                    triggerFocus();
                }, CONST.SCREEN_TRANSITION_END_TIMEOUT);
    
                return () => {
                    isCancelled = true;
                    if (timeoutId) {
                        clearTimeout(timeoutId);
                        timeoutId = null;
                    }
                    if (unsubscribe) {
                        unsubscribe();
                    }
                    if (focusTaskHandle) {
                        focusTaskHandle.cancel();
                    }
                };
            }, [shouldDelayAutoFocus, ref, isInLandscapeMode, navigation]),
        );
    }
    
    export default useDelayedAutoFocus;

    Why this completely resolves the issue:

    1. No premature focus: Focus is deferred until the screen transition and any overlapping native modal dismissals have fully settled.
    2. No involuntary blur: Because focus is only applied once the view hierarchy is stable, iOS does not resign the first responder immediately after focus.
    3. No premature validation: Without an involuntary blur, FormProvider's onBlur does not fire, keeping the field untouched until the user actually interacts or submits.
    4. App-wide coverage: Fixes all substeps across USD and Non-USD bank account verification flows that rely on SingleFieldStep and RegistrationNumberStep with shouldDelayAutoFocus.

    What alternative solutions did you explore? (Optional)

    1. Delaying onSubmit in VerifyIdentity.tsx: We could delay navigation in handleOnfidoSuccess until after the Onfido modal dismisses. However, this only patches the Onfido transition and does not protect against general navigation transition race conditions on slower devices.
    2. Preventing blur validation on untouched fields in FormProvider.tsx: We considered modifying FormProvider's onBlur handler to ignore blurs if the user never entered keystrokes. However, this alters form validation behavior globally across the entire app and carries high regression risk compared to fixing the autofocus timing hook.

    Unit / Regression Tests

    • Unit test: Add unit tests in tests/unit/useDelayedAutoFocusTest.ts to verify that useDelayedAutoFocus:

      1. Does not trigger focus when shouldDelayAutoFocus is false.
      2. Waits for transitionEnd event before invoking ref.current.focus().
      3. Correctly cleans up transitionEnd listener and cancels TransitionTracker handle on screen blur / unmount.
      4. Respects tryClaim(Priorities.AUTO) and shouldSkipAutoFocusDueToExistingFocus().
    • Manual QA Steps:

      1. Sign in to NewDot on an iOS device.
      2. Create a workspace and navigate to Workflows > Payments.
      3. Tap Add bank account > Connect bank > select any bank and account.
      4. Fill in Personal Details (First Name, Last Name, SSN, Address, etc.) and tap Continue.
      5. Complete Onfido verification and tap Submit video.
      6. Verify that upon landing on the "Enter the name of your business" step, the company name input is focused cleanly and NO red error "This field is required" is displayed.
      7. Clear the field (or leave empty) and tap Next.
      8. Verify that on explicit submission, "This field is required" validation still correctly triggers.
  12. melvin-bot commented on Oct 14, 2026

    @melvin-bot

    ✅ Contributor details stored successfully. Thank you for contributing to Expensify!

  13. 39 remaining items

  14. added
    Awaiting PaymentAuto-added when associated PR is deployed to production
    and removed on Nov 9, 2026
  15. melvin-bot commented on Nov 9, 2026

    @melvin-bot

    Triggered auto assignment to @mallenexpensify (Awaiting Payment)

  16. melvin-bot commented on Nov 9, 2026

    @melvin-bot

    Payment Summary

    Resolving PRs:

    BugZero Checklist (@mallenexpensify)

    • I have confirmed assignees, roles, and Upwork contracts look correct
    • I have paid out Upwork contracts / manual NewDot requests
    • [BugZero Assignee] I have created a GH issue for creating/updating the regression test once above steps have been agreed upon
  17. melvin-bot commented on Nov 12, 2026

    @melvin-bot

    @mallenexpensify Whoops! This issue is 2 days overdue. Let's get this updated quick!

  18. mallenexpensify commented on Nov 13, 2026

    @mallenexpensify
    Contributor

    Payment Summary

    Contributor+: @mkhutornyi due $175 via NewDot

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Awaiting PaymentAuto-added when associated PR is deployed to productionBugSomething is broken. Auto assigns a BugZero manager.DailyKSv2ExternalAdded to denote the issue can be worked on by a contributor

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions