From cab9c869faa70a8fda49bae30a211433272cccc4 Mon Sep 17 00:00:00 2001
From: selvan
Date: Sun, 1 Feb 2026 22:07:15 +0530
Subject: [PATCH 1/6] fix the useRecaptchaVerifier hook race condition
---
.../mfa/sms-multi-factor-assertion-form.tsx | 8 ++++++--
.../mfa/sms-multi-factor-enrollment-form.tsx | 8 ++++++--
.../react/src/auth/forms/phone-auth-form.tsx | 9 +++++++--
packages/react/src/hooks.ts | 18 ++++++++++++------
4 files changed, 31 insertions(+), 12 deletions(-)
diff --git a/packages/react/src/auth/forms/mfa/sms-multi-factor-assertion-form.tsx b/packages/react/src/auth/forms/mfa/sms-multi-factor-assertion-form.tsx
index 761e9514..ce7a4d71 100644
--- a/packages/react/src/auth/forms/mfa/sms-multi-factor-assertion-form.tsx
+++ b/packages/react/src/auth/forms/mfa/sms-multi-factor-assertion-form.tsx
@@ -110,7 +110,9 @@ function SmsMultiFactorAssertionPhoneForm(props: SmsMultiFactorAssertionPhoneFor
onSubmit={async (e) => {
e.preventDefault();
e.stopPropagation();
- await form.handleSubmit();
+ if (recaptchaVerifier) {
+ await form.handleSubmit();
+ }
}}
>
@@ -127,7 +129,9 @@ function SmsMultiFactorAssertionPhoneForm(props: SmsMultiFactorAssertionPhoneFor
diff --git a/packages/react/src/auth/forms/mfa/sms-multi-factor-enrollment-form.tsx b/packages/react/src/auth/forms/mfa/sms-multi-factor-enrollment-form.tsx
index 3b383955..ae54edca 100644
--- a/packages/react/src/auth/forms/mfa/sms-multi-factor-enrollment-form.tsx
+++ b/packages/react/src/auth/forms/mfa/sms-multi-factor-enrollment-form.tsx
@@ -114,7 +114,9 @@ function MultiFactorEnrollmentPhoneNumberForm(props: MultiFactorEnrollmentPhoneN
onSubmit={async (e) => {
e.preventDefault();
e.stopPropagation();
- await form.handleSubmit();
+ if (recaptchaVerifier) {
+ await form.handleSubmit();
+ }
}}
>
@@ -138,7 +140,9 @@ function MultiFactorEnrollmentPhoneNumberForm(props: MultiFactorEnrollmentPhoneN
diff --git a/packages/react/src/auth/forms/phone-auth-form.tsx b/packages/react/src/auth/forms/phone-auth-form.tsx
index 0553c457..8ba05ed5 100644
--- a/packages/react/src/auth/forms/phone-auth-form.tsx
+++ b/packages/react/src/auth/forms/phone-auth-form.tsx
@@ -103,6 +103,7 @@ export function PhoneNumberForm(props: PhoneNumberFormProps) {
const recaptchaContainerRef = useRef(null);
const recaptchaVerifier = useRecaptchaVerifier(recaptchaContainerRef);
const countrySelector = useRef(null);
+
const form = usePhoneNumberForm({
recaptchaVerifier: recaptchaVerifier!,
onSuccess: props.onSubmit,
@@ -115,7 +116,9 @@ export function PhoneNumberForm(props: PhoneNumberFormProps) {
onSubmit={async (e) => {
e.preventDefault();
e.stopPropagation();
- await form.handleSubmit();
+ if (recaptchaVerifier) {
+ await form.handleSubmit();
+ }
}}
>
@@ -135,7 +138,9 @@ export function PhoneNumberForm(props: PhoneNumberFormProps) {
diff --git a/packages/react/src/hooks.ts b/packages/react/src/hooks.ts
index 75cb777c..344d1cd2 100644
--- a/packages/react/src/hooks.ts
+++ b/packages/react/src/hooks.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { useContext, useMemo, useEffect, useRef } from "react";
+import { useContext, useMemo, useEffect, useRef, useState } from "react";
import type { RecaptchaVerifier, User } from "firebase/auth";
import {
createEmailLinkAuthFormSchema,
@@ -200,7 +200,7 @@ export function useMultiFactorTotpAuthVerifyFormSchema() {
*/
export function useRecaptchaVerifier(ref: React.RefObject) {
const ui = useUI();
- const verifierRef = useRef(null);
+ const [verifier, setVerifier] = useState(null);
const uiRef = useRef(ui);
const prevElementRef = useRef(null);
@@ -213,13 +213,19 @@ export function useRecaptchaVerifier(ref: React.RefObject
if (currentElement !== prevElementRef.current) {
prevElementRef.current = currentElement;
if (currentElement) {
- verifierRef.current = getBehavior(currentUI, "recaptchaVerification")(currentUI, currentElement);
- verifierRef.current.render();
+ try {
+ const newVerifier = getBehavior(currentUI, "recaptchaVerification")(currentUI, currentElement);
+ newVerifier.render();
+ setVerifier(newVerifier);
+ } catch (error) {
+ console.error('[useRecaptchaVerifier] Failed to create/render verifier:', error);
+ setVerifier(null);
+ }
} else {
- verifierRef.current = null;
+ setVerifier(null);
}
}
}, [ref]);
- return verifierRef.current;
+ return verifier;
}
From 2b93e9c23e4ccfeedf6d7b6bed2cbe5d2315ca41 Mon Sep 17 00:00:00 2001
From: selvan
Date: Tue, 3 Feb 2026 19:32:51 +0530
Subject: [PATCH 2/6] Changes related to pnpm lint:fix
---
packages/react/src/hooks.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/react/src/hooks.ts b/packages/react/src/hooks.ts
index 344d1cd2..e4531564 100644
--- a/packages/react/src/hooks.ts
+++ b/packages/react/src/hooks.ts
@@ -218,7 +218,7 @@ export function useRecaptchaVerifier(ref: React.RefObject
newVerifier.render();
setVerifier(newVerifier);
} catch (error) {
- console.error('[useRecaptchaVerifier] Failed to create/render verifier:', error);
+ console.error("[useRecaptchaVerifier] Failed to create/render verifier:", error);
setVerifier(null);
}
} else {
From eaa1f6fbbd932880d9fc6673cf21a42e7464947d Mon Sep 17 00:00:00 2001
From: MichaelVerdon
Date: Tue, 3 Feb 2026 15:27:52 +0000
Subject: [PATCH 3/6] test: add test for phone auth form verification code
screen transition
---
.../src/auth/forms/phone-auth-form.test.tsx | 36 +++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/packages/react/src/auth/forms/phone-auth-form.test.tsx b/packages/react/src/auth/forms/phone-auth-form.test.tsx
index 7bd9d6fb..0bc2f5e2 100644
--- a/packages/react/src/auth/forms/phone-auth-form.test.tsx
+++ b/packages/react/src/auth/forms/phone-auth-form.test.tsx
@@ -78,6 +78,7 @@ import { verifyPhoneNumber, confirmPhoneNumber } from "@firebase-oss/ui-core";
import { createFirebaseUIProvider, createMockUI } from "~/tests/utils";
import { registerLocale } from "@firebase-oss/ui-translations";
import { FirebaseUIProvider } from "~/context";
+import { useRecaptchaVerifier } from "~/hooks";
vi.mock("~/components/country-selector", () => ({
CountrySelector: vi.fn().mockImplementation(({ value, onChange, ref }: any) => {
@@ -578,6 +579,16 @@ describe("", () => {
const mockVerificationId = "test-verification-id";
vi.mocked(verifyPhoneNumber).mockResolvedValue(mockVerificationId);
+ // Create a mock verifier that simulates async render()
+ const mockVerifier = {
+ render: vi.fn().mockResolvedValue(123),
+ clear: vi.fn(),
+ verify: vi.fn().mockResolvedValue("verification-token"),
+ };
+
+ // Override the global mock to return our specific verifier
+ vi.mocked(useRecaptchaVerifier).mockReturnValue(mockVerifier as unknown as import("firebase/auth").RecaptchaVerifier);
+
const { container } = render(
@@ -591,9 +602,34 @@ describe("", () => {
await act(async () => {
fireEvent.change(phoneInput, { target: { value: "1234567890" } });
+ });
+
+ // Check if there are any validation errors before submitting
+ const errorBeforeSubmit = screen.queryByTestId("error-message");
+ if (errorBeforeSubmit) {
+ throw new Error(`Form has validation error before submit: ${errorBeforeSubmit.textContent}`);
+ }
+
+ await act(async () => {
fireEvent.click(sendCodeButton);
});
+ // Wait for the async form submission to complete
+ await waitFor(
+ () => {
+ expect(verifyPhoneNumber).toHaveBeenCalled();
+ },
+ { timeout: 3000 }
+ );
+
+ // Verify that verifyPhoneNumber was called with the verifier
+ // Note: The phone number gets formatted, so we check for the formatted version
+ expect(verifyPhoneNumber).toHaveBeenCalledWith(
+ expect.anything(),
+ expect.stringMatching(/1.*234.*567.*890/), // Matches formatted phone number like "1(234)567-890" or "+11234567890"
+ mockVerifier
+ );
+
const verificationInput = await waitFor(() => {
return screen.getByRole("textbox", { name: /verificationCode/i });
});
From d566f74e183c86d0323d29408e4d70a3a24c729b Mon Sep 17 00:00:00 2001
From: MichaelVerdon
Date: Tue, 3 Feb 2026 15:31:05 +0000
Subject: [PATCH 4/6] chore: trigger CI
From 83fed60d1fc133acd172c68080d4f680dfb9b18d Mon Sep 17 00:00:00 2001
From: MichaelVerdon
Date: Tue, 3 Feb 2026 15:44:00 +0000
Subject: [PATCH 5/6] chore: apply lint fixes
---
packages/react/src/auth/forms/phone-auth-form.test.tsx | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/packages/react/src/auth/forms/phone-auth-form.test.tsx b/packages/react/src/auth/forms/phone-auth-form.test.tsx
index 0bc2f5e2..ffb83d20 100644
--- a/packages/react/src/auth/forms/phone-auth-form.test.tsx
+++ b/packages/react/src/auth/forms/phone-auth-form.test.tsx
@@ -587,7 +587,9 @@ describe("", () => {
};
// Override the global mock to return our specific verifier
- vi.mocked(useRecaptchaVerifier).mockReturnValue(mockVerifier as unknown as import("firebase/auth").RecaptchaVerifier);
+ vi.mocked(useRecaptchaVerifier).mockReturnValue(
+ mockVerifier as unknown as import("firebase/auth").RecaptchaVerifier
+ );
const { container } = render(
From a79ef1a09d9620c4589151d6236904ebba031a79 Mon Sep 17 00:00:00 2001
From: MichaelVerdon
Date: Tue, 3 Feb 2026 15:44:10 +0000
Subject: [PATCH 6/6] chore: trigger CI