diff --git a/packages/angular/src/lib/auth/forms/email-link-auth-form.ts b/packages/angular/src/lib/auth/forms/email-link-auth-form.ts
index 0e7f0709..2d1d984f 100644
--- a/packages/angular/src/lib/auth/forms/email-link-auth-form.ts
+++ b/packages/angular/src/lib/auth/forms/email-link-auth-form.ts
@@ -106,7 +106,7 @@ export class EmailLinkAuthFormComponent {
effect(() => {
this.form.update({
validators: {
- onBlur: this.formSchema(),
+ onChange: this.formSchema(),
onSubmitAsync: async ({ value }) => {
try {
await sendSignInLinkToEmail(this.ui(), value.email);
diff --git a/packages/angular/src/lib/auth/forms/forgot-password-auth-form.ts b/packages/angular/src/lib/auth/forms/forgot-password-auth-form.ts
index e45e6509..da0d44ec 100644
--- a/packages/angular/src/lib/auth/forms/forgot-password-auth-form.ts
+++ b/packages/angular/src/lib/auth/forms/forgot-password-auth-form.ts
@@ -114,7 +114,7 @@ export class ForgotPasswordAuthFormComponent {
effect(() => {
this.form.update({
validators: {
- onBlur: this.formSchema(),
+ onChange: this.formSchema(),
onSubmitAsync: async ({ value }) => {
try {
await sendPasswordResetEmail(this.ui(), value.email);
diff --git a/packages/angular/src/lib/auth/forms/mfa/sms-multi-factor-assertion-form.ts b/packages/angular/src/lib/auth/forms/mfa/sms-multi-factor-assertion-form.ts
index 143ef207..49b32bea 100644
--- a/packages/angular/src/lib/auth/forms/mfa/sms-multi-factor-assertion-form.ts
+++ b/packages/angular/src/lib/auth/forms/mfa/sms-multi-factor-assertion-form.ts
@@ -203,7 +203,7 @@ export class SmsMultiFactorAssertionVerifyFormComponent {
effect(() => {
this.form.update({
validators: {
- onBlur: this.formSchema(),
+ onChange: this.formSchema(),
onSubmit: this.formSchema(),
onSubmitAsync: async ({ value }) => {
try {
diff --git a/packages/angular/src/lib/auth/forms/mfa/sms-multi-factor-enrollment-form.ts b/packages/angular/src/lib/auth/forms/mfa/sms-multi-factor-enrollment-form.ts
index 680244e5..af8d72f5 100644
--- a/packages/angular/src/lib/auth/forms/mfa/sms-multi-factor-enrollment-form.ts
+++ b/packages/angular/src/lib/auth/forms/mfa/sms-multi-factor-enrollment-form.ts
@@ -164,7 +164,7 @@ export class SmsMultiFactorEnrollmentFormComponent {
effect(() => {
this.phoneForm.update({
validators: {
- onBlur: this.phoneFormSchema(),
+ onChange: this.phoneFormSchema(),
onSubmit: this.phoneFormSchema(),
onSubmitAsync: async ({ value }) => {
try {
@@ -192,7 +192,7 @@ export class SmsMultiFactorEnrollmentFormComponent {
effect(() => {
this.verificationForm.update({
validators: {
- onBlur: this.verificationFormSchema(),
+ onChange: this.verificationFormSchema(),
onSubmit: this.verificationFormSchema(),
onSubmitAsync: async ({ value }) => {
try {
diff --git a/packages/angular/src/lib/auth/forms/mfa/totp-multi-factor-assertion-form.ts b/packages/angular/src/lib/auth/forms/mfa/totp-multi-factor-assertion-form.ts
index d9b08e92..c6d6c479 100644
--- a/packages/angular/src/lib/auth/forms/mfa/totp-multi-factor-assertion-form.ts
+++ b/packages/angular/src/lib/auth/forms/mfa/totp-multi-factor-assertion-form.ts
@@ -88,7 +88,7 @@ export class TotpMultiFactorAssertionFormComponent {
effect(() => {
this.form.update({
validators: {
- onBlur: this.formSchema(),
+ onChange: this.formSchema(),
onSubmitAsync: async ({ value }) => {
try {
const assertion = TotpMultiFactorGenerator.assertionForSignIn(this.hint().uid, value.verificationCode);
diff --git a/packages/angular/src/lib/auth/forms/mfa/totp-multi-factor-enrollment-form.ts b/packages/angular/src/lib/auth/forms/mfa/totp-multi-factor-enrollment-form.ts
index 936690eb..a9b2caa6 100644
--- a/packages/angular/src/lib/auth/forms/mfa/totp-multi-factor-enrollment-form.ts
+++ b/packages/angular/src/lib/auth/forms/mfa/totp-multi-factor-enrollment-form.ts
@@ -91,7 +91,7 @@ export class TotpMultiFactorSecretGenerationFormComponent {
effect(() => {
this.form.update({
validators: {
- onBlur: this.formSchema(),
+ onChange: this.formSchema(),
onSubmit: this.formSchema(),
onSubmitAsync: async ({ value }) => {
try {
@@ -191,7 +191,7 @@ export class TotpMultiFactorVerificationFormComponent {
effect(() => {
this.form.update({
validators: {
- onBlur: this.formSchema(),
+ onChange: this.formSchema(),
onSubmit: this.formSchema(),
onSubmitAsync: async ({ value }) => {
try {
diff --git a/packages/angular/src/lib/auth/forms/phone-auth-form.ts b/packages/angular/src/lib/auth/forms/phone-auth-form.ts
index b87d10fe..88e57422 100644
--- a/packages/angular/src/lib/auth/forms/phone-auth-form.ts
+++ b/packages/angular/src/lib/auth/forms/phone-auth-form.ts
@@ -110,7 +110,7 @@ export class PhoneNumberFormComponent {
effect(() => {
this.form.update({
validators: {
- onBlur: this.formSchema(),
+ onChange: this.formSchema(),
onSubmitAsync: async ({ value }) => {
const selectedCountry = countryData.find((c) => c.code === this.country());
const formattedNumber = formatPhoneNumber(value.phoneNumber, selectedCountry!);
@@ -226,7 +226,7 @@ export class VerificationFormComponent {
effect(() => {
this.form.update({
validators: {
- onBlur: this.formSchema(),
+ onChange: this.formSchema(),
onSubmitAsync: async ({ value }) => {
try {
const credential = await confirmPhoneNumber(this.ui(), this.verificationId(), value.verificationCode);
diff --git a/packages/angular/src/lib/auth/forms/sign-in-auth-form.ts b/packages/angular/src/lib/auth/forms/sign-in-auth-form.ts
index a1fa5c12..b6547923 100644
--- a/packages/angular/src/lib/auth/forms/sign-in-auth-form.ts
+++ b/packages/angular/src/lib/auth/forms/sign-in-auth-form.ts
@@ -129,7 +129,7 @@ export class SignInAuthFormComponent {
effect(() => {
this.form.update({
validators: {
- onBlur: this.formSchema(),
+ onChange: this.formSchema(),
onSubmitAsync: async ({ value }) => {
try {
const credential = await signInWithEmailAndPassword(this.ui(), value.email, value.password);
diff --git a/packages/angular/src/lib/auth/forms/sign-up-auth-form.ts b/packages/angular/src/lib/auth/forms/sign-up-auth-form.ts
index 498d985c..5d455dce 100644
--- a/packages/angular/src/lib/auth/forms/sign-up-auth-form.ts
+++ b/packages/angular/src/lib/auth/forms/sign-up-auth-form.ts
@@ -125,7 +125,7 @@ export class SignUpAuthFormComponent {
effect(() => {
this.form.update({
validators: {
- onBlur: this.formSchema(),
+ onChange: this.formSchema(),
onSubmitAsync: async ({ value }) => {
try {
const credential = await createUserWithEmailAndPassword(
diff --git a/packages/angular/src/lib/components/form.spec.ts b/packages/angular/src/lib/components/form.spec.ts
index 34372808..b0d50bdf 100644
--- a/packages/angular/src/lib/components/form.spec.ts
+++ b/packages/angular/src/lib/components/form.spec.ts
@@ -28,19 +28,13 @@ import {
import { ButtonComponent } from "./button";
@Component({
- template: ``,
+ template: ``,
standalone: true,
imports: [FormMetadataComponent],
})
class TestFormMetadataHostComponent {
- field = signal({
- state: {
- meta: {
- isTouched: true,
- errors: [{ message: "Test error" }],
- },
- },
- } as any);
+ isTouched = signal(true);
+ errors = signal([{ message: "Test error" }]);
}
@Component({
@@ -90,14 +84,7 @@ describe("Form Components", () => {
it("does not render error message when field has no errors", async () => {
const component = await render(TestFormMetadataHostComponent);
- component.fixture.componentInstance.field.set({
- state: {
- meta: {
- isTouched: true,
- errors: [],
- },
- },
- } as any);
+ component.fixture.componentInstance.errors.set([]);
component.fixture.detectChanges();
const errorElement = screen.queryByRole("alert");
@@ -107,14 +94,7 @@ describe("Form Components", () => {
it("does not render error message when field is not touched", async () => {
const component = await render(TestFormMetadataHostComponent);
- component.fixture.componentInstance.field.set({
- state: {
- meta: {
- isTouched: false,
- errors: [{ message: "Test error" }],
- },
- },
- } as any);
+ component.fixture.componentInstance.isTouched.set(false);
component.fixture.detectChanges();
const errorElement = screen.queryByRole("alert");
diff --git a/packages/angular/src/lib/components/form.ts b/packages/angular/src/lib/components/form.ts
index 856a96fa..d9db7406 100644
--- a/packages/angular/src/lib/components/form.ts
+++ b/packages/angular/src/lib/components/form.ts
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-import { Component, computed, input } from "@angular/core";
-import { AnyFieldApi, AnyFormState, injectField } from "@tanstack/angular-form";
+import { ChangeDetectorRef, Component, computed, inject, input, OnChanges, SimpleChanges } from "@angular/core";
+import { AnyFormState, injectField } from "@tanstack/angular-form";
import { ButtonComponent } from "./button";
@Component({
@@ -25,10 +25,10 @@ import { ButtonComponent } from "./button";
style: "display: block;",
},
template: `
- @if (field().state.meta.isTouched && errors().length > 0) {
+ @if (isTouched() && errors().length > 0) {
- {{ errors() }}
+ {{ errorMessage() }}
}
@@ -38,13 +38,14 @@ import { ButtonComponent } from "./button";
* A component that displays form field metadata, such as validation errors.
*/
export class FormMetadataComponent {
- /** The form field API instance. */
- field = input.required();
- errors = computed(() =>
- this.field()
- .state.meta.errors.map((error) => error.message)
- .join(", ")
- );
+ isTouched = input.required();
+ errors = input.required>();
+
+ errorMessage(): string {
+ return this.errors()
+ .map((error) => error.message)
+ .join(", ");
+ }
}
@Component({
@@ -70,27 +71,35 @@ export class FormMetadataComponent {
[id]="field.api.name"
[name]="field.api.name"
[value]="field.api.state.value"
- (blur)="field.api.handleBlur()"
(input)="field.api.handleChange($any($event).target.value)"
[type]="type()"
/>
-
+
`,
})
/**
* A form input component with label, description, and validation support.
*/
-export class FormInputComponent {
+export class FormInputComponent implements OnChanges {
field = injectField();
+ private cdr = inject(ChangeDetectorRef);
/** The label text for the input field. */
label = input.required();
/** The input type (e.g., "text", "email", "password"). */
type = input("text");
/** Optional description text displayed below the label. */
description = input();
+
+ ngOnChanges(_changes: SimpleChanges): void {
+ // Trigger change detection when any input changes
+ this.cdr.markForCheck();
+ }
}
@Component({
diff --git a/packages/core/src/schemas.ts b/packages/core/src/schemas.ts
index 2e03b42c..255b8ab6 100644
--- a/packages/core/src/schemas.ts
+++ b/packages/core/src/schemas.ts
@@ -29,7 +29,7 @@ import { hasBehavior } from "./behaviors";
*/
export function createSignInAuthFormSchema(ui: FirebaseUI) {
return z.object({
- email: z.email(getTranslation(ui, "errors", "invalidEmail")),
+ email: z.string().email(getTranslation(ui, "errors", "invalidEmail")),
password: z.string().min(6, getTranslation(ui, "errors", "weakPassword")),
});
}
@@ -48,7 +48,7 @@ export function createSignUpAuthFormSchema(ui: FirebaseUI) {
const displayNameRequiredMessage = getTranslation(ui, "errors", "displayNameRequired");
return z.object({
- email: z.email(getTranslation(ui, "errors", "invalidEmail")),
+ email: z.string().email(getTranslation(ui, "errors", "invalidEmail")),
password: z.string().min(6, getTranslation(ui, "errors", "weakPassword")),
displayName: requireDisplayName
? z.string().min(1, displayNameRequiredMessage)
@@ -66,7 +66,7 @@ export function createSignUpAuthFormSchema(ui: FirebaseUI) {
*/
export function createForgotPasswordAuthFormSchema(ui: FirebaseUI) {
return z.object({
- email: z.email(getTranslation(ui, "errors", "invalidEmail")),
+ email: z.string().email(getTranslation(ui, "errors", "invalidEmail")),
});
}
@@ -80,7 +80,7 @@ export function createForgotPasswordAuthFormSchema(ui: FirebaseUI) {
*/
export function createEmailLinkAuthFormSchema(ui: FirebaseUI) {
return z.object({
- email: z.email(getTranslation(ui, "errors", "invalidEmail")),
+ email: z.string().email(getTranslation(ui, "errors", "invalidEmail")),
});
}
diff --git a/packages/react/src/auth/forms/email-link-auth-form.test.tsx b/packages/react/src/auth/forms/email-link-auth-form.test.tsx
index 4e358cc6..6181be94 100644
--- a/packages/react/src/auth/forms/email-link-auth-form.test.tsx
+++ b/packages/react/src/auth/forms/email-link-auth-form.test.tsx
@@ -292,7 +292,7 @@ describe("", () => {
expect(onSignInMock).not.toHaveBeenCalled();
});
- it("should trigger validation errors when the form is blurred", () => {
+ it("should trigger validation errors when the form changes", async () => {
const mockUI = createMockUI();
const { container } = render(
@@ -307,9 +307,9 @@ describe("", () => {
const input = screen.getByRole("textbox", { name: /email/i });
act(() => {
- fireEvent.blur(input);
+ fireEvent.change(input, { target: { value: "invalid" } });
});
- expect(screen.getByText("Please enter a valid email address")).toBeInTheDocument();
+ expect(await screen.findByText("Please enter a valid email address")).toBeInTheDocument();
});
});
diff --git a/packages/react/src/auth/forms/email-link-auth-form.tsx b/packages/react/src/auth/forms/email-link-auth-form.tsx
index 028f25df..064a2e97 100644
--- a/packages/react/src/auth/forms/email-link-auth-form.tsx
+++ b/packages/react/src/auth/forms/email-link-auth-form.tsx
@@ -71,7 +71,7 @@ export function useEmailLinkAuthForm(onSuccess?: EmailLinkAuthFormProps["onEmail
email: "",
},
validators: {
- onBlur: schema,
+ onChange: schema,
onSubmitAsync: async ({ value }) => {
try {
await action(value);
diff --git a/packages/react/src/auth/forms/forgot-password-auth-form.test.tsx b/packages/react/src/auth/forms/forgot-password-auth-form.test.tsx
index 3fe18a8f..ca49549e 100644
--- a/packages/react/src/auth/forms/forgot-password-auth-form.test.tsx
+++ b/packages/react/src/auth/forms/forgot-password-auth-form.test.tsx
@@ -198,7 +198,7 @@ describe("", () => {
expect(onBackToSignInClickMock).toHaveBeenCalled();
});
- it("should trigger validation errors when the form is blurred", () => {
+ it("should trigger validation errors when the form changes", () => {
const mockUI = createMockUI();
const { container } = render(
@@ -213,7 +213,7 @@ describe("", () => {
const input = screen.getByRole("textbox", { name: /email/i });
act(() => {
- fireEvent.blur(input);
+ fireEvent.change(input, { target: { value: "invalid" } });
});
expect(screen.getByText("Please enter a valid email address")).toBeInTheDocument();
diff --git a/packages/react/src/auth/forms/forgot-password-auth-form.tsx b/packages/react/src/auth/forms/forgot-password-auth-form.tsx
index ff6937ed..f0e7ce0e 100644
--- a/packages/react/src/auth/forms/forgot-password-auth-form.tsx
+++ b/packages/react/src/auth/forms/forgot-password-auth-form.tsx
@@ -70,7 +70,7 @@ export function useForgotPasswordAuthForm(onSuccess?: ForgotPasswordAuthFormProp
email: "",
},
validators: {
- onBlur: schema,
+ onChange: schema,
onSubmitAsync: async ({ value }) => {
try {
await action(value);
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..055c0ca4 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
@@ -180,7 +180,7 @@ export function useSmsMultiFactorAssertionVerifyForm({
verificationCode: "",
},
validators: {
- onBlur: schema,
+ onChange: schema,
onSubmitAsync: async ({ value }) => {
try {
const credential = await action(value);
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..5612663a 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
@@ -79,7 +79,7 @@ export function useSmsMultiFactorEnrollmentPhoneNumberForm({
phoneNumber: "",
},
validators: {
- onBlur: schema,
+ onChange: schema,
onSubmitAsync: async ({ value }) => {
try {
const formatted = formatPhoneNumber ? formatPhoneNumber(value.phoneNumber) : value.phoneNumber;
@@ -201,7 +201,7 @@ export function useMultiFactorEnrollmentVerifyPhoneNumberForm({
verificationCode: "",
},
validators: {
- onBlur: schema,
+ onChange: schema,
onSubmitAsync: async ({ value }) => {
try {
await action({ ...value, displayName });
diff --git a/packages/react/src/auth/forms/mfa/totp-multi-factor-assertion-form.tsx b/packages/react/src/auth/forms/mfa/totp-multi-factor-assertion-form.tsx
index acdb607b..efc9aabd 100644
--- a/packages/react/src/auth/forms/mfa/totp-multi-factor-assertion-form.tsx
+++ b/packages/react/src/auth/forms/mfa/totp-multi-factor-assertion-form.tsx
@@ -60,7 +60,7 @@ export function useTotpMultiFactorAssertionForm({ hint, onSuccess }: UseTotpMult
verificationCode: "",
},
validators: {
- onBlur: schema,
+ onChange: schema,
onSubmitAsync: async ({ value }) => {
try {
const credential = await action({ verificationCode: value.verificationCode, hint });
diff --git a/packages/react/src/auth/forms/mfa/totp-multi-factor-enrollment-form.tsx b/packages/react/src/auth/forms/mfa/totp-multi-factor-enrollment-form.tsx
index 6581875a..a1ddf6aa 100644
--- a/packages/react/src/auth/forms/mfa/totp-multi-factor-enrollment-form.tsx
+++ b/packages/react/src/auth/forms/mfa/totp-multi-factor-enrollment-form.tsx
@@ -60,7 +60,7 @@ export function useTotpMultiFactorSecretGenerationForm({ onSuccess }: UseTotpMul
displayName: "",
},
validators: {
- onBlur: schema,
+ onChange: schema,
onSubmitAsync: async ({ value }) => {
try {
const secret = await action();
@@ -160,7 +160,7 @@ export function useMultiFactorEnrollmentVerifyTotpForm({
verificationCode: "",
},
validators: {
- onBlur: schema,
+ onChange: schema,
onSubmitAsync: async ({ value }) => {
try {
await action({ secret, verificationCode: value.verificationCode, displayName });
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..ea8580bf 100644
--- a/packages/react/src/auth/forms/phone-auth-form.test.tsx
+++ b/packages/react/src/auth/forms/phone-auth-form.test.tsx
@@ -479,7 +479,7 @@ describe("", () => {
expect(sendCodeButton).toHaveAttribute("type", "submit");
});
- it("should trigger validation errors when the form is blurred", () => {
+ it("should trigger validation errors when the form changes", () => {
const mockUI = createMockUI();
const { container } = render(
@@ -494,7 +494,8 @@ describe("", () => {
const input = screen.getByRole("textbox", { name: /phone number/i });
act(() => {
- fireEvent.blur(input);
+ fireEvent.change(input, { target: { value: "1" } });
+ fireEvent.change(input, { target: { value: "" } });
});
expect(screen.getByText("Please provide a phone number")).toBeInTheDocument();
diff --git a/packages/react/src/auth/forms/phone-auth-form.tsx b/packages/react/src/auth/forms/phone-auth-form.tsx
index 0553c457..09262197 100644
--- a/packages/react/src/auth/forms/phone-auth-form.tsx
+++ b/packages/react/src/auth/forms/phone-auth-form.tsx
@@ -71,7 +71,7 @@ export function usePhoneNumberForm({ recaptchaVerifier, onSuccess, formatPhoneNu
phoneNumber: "",
},
validators: {
- onBlur: schema,
+ onChange: schema,
onSubmitAsync: async ({ value }) => {
try {
const formatted = formatPhoneNumber ? formatPhoneNumber(value.phoneNumber) : value.phoneNumber;
@@ -183,7 +183,7 @@ export function useVerifyPhoneNumberForm({ verificationId, onSuccess }: UseVerif
verificationCode: "",
},
validators: {
- onBlur: schema,
+ onChange: schema,
onSubmitAsync: async ({ value }) => {
try {
const credential = await action(value);
diff --git a/packages/react/src/auth/forms/sign-in-auth-form.test.tsx b/packages/react/src/auth/forms/sign-in-auth-form.test.tsx
index fff23639..57fe6bd4 100644
--- a/packages/react/src/auth/forms/sign-in-auth-form.test.tsx
+++ b/packages/react/src/auth/forms/sign-in-auth-form.test.tsx
@@ -262,7 +262,7 @@ describe("", () => {
expect(onSignUpClick).toHaveBeenCalled();
});
- it("should trigger validation errors when the form is blurred", () => {
+ it("should trigger validation errors when the form changes", () => {
const mockUI = createMockUI();
const { container } = render(
@@ -277,7 +277,7 @@ describe("", () => {
const input = screen.getByRole("textbox", { name: /email/i });
act(() => {
- fireEvent.blur(input);
+ fireEvent.change(input, { target: { value: "invalid" } });
});
expect(screen.getByText("Please enter a valid email address")).toBeInTheDocument();
diff --git a/packages/react/src/auth/forms/sign-in-auth-form.tsx b/packages/react/src/auth/forms/sign-in-auth-form.tsx
index 488a0462..97cef95d 100644
--- a/packages/react/src/auth/forms/sign-in-auth-form.tsx
+++ b/packages/react/src/auth/forms/sign-in-auth-form.tsx
@@ -74,7 +74,7 @@ export function useSignInAuthForm(onSuccess?: SignInAuthFormProps["onSignIn"]) {
password: "",
},
validators: {
- onBlur: schema,
+ onChange: schema,
onSubmitAsync: async ({ value }) => {
try {
const credential = await action(value);
diff --git a/packages/react/src/auth/forms/sign-up-auth-form.test.tsx b/packages/react/src/auth/forms/sign-up-auth-form.test.tsx
index 0d3cdf53..049a6dcd 100644
--- a/packages/react/src/auth/forms/sign-up-auth-form.test.tsx
+++ b/packages/react/src/auth/forms/sign-up-auth-form.test.tsx
@@ -315,7 +315,7 @@ describe("", () => {
expect(onSignInClickMock).toHaveBeenCalled();
});
- it("should trigger validation errors when the form is blurred", () => {
+ it("should trigger validation errors when the form changes", () => {
const mockUI = createMockUI();
const { container } = render(
@@ -330,7 +330,7 @@ describe("", () => {
const input = screen.getByRole("textbox", { name: /email/i });
act(() => {
- fireEvent.blur(input);
+ fireEvent.change(input, { target: { value: "invalid" } });
});
expect(screen.getByText("Please enter a valid email address")).toBeInTheDocument();
@@ -401,7 +401,7 @@ describe("", () => {
expect(screen.queryByRole("textbox", { name: /displayName/ })).not.toBeInTheDocument();
});
- it("should trigger displayName validation errors when the form is blurred and requireDisplayName is enabled", () => {
+ it("should trigger displayName validation errors when the form changes and requireDisplayName is enabled", () => {
const mockUI = createMockUI({
locale: registerLocale("test", {
errors: {
@@ -431,7 +431,8 @@ describe("", () => {
expect(displayNameInput).toBeInTheDocument();
act(() => {
- fireEvent.blur(displayNameInput);
+ fireEvent.change(displayNameInput, { target: { value: "a" } });
+ fireEvent.change(displayNameInput, { target: { value: "" } });
});
expect(screen.getByText("Please provide a display name")).toBeInTheDocument();
diff --git a/packages/react/src/auth/forms/sign-up-auth-form.tsx b/packages/react/src/auth/forms/sign-up-auth-form.tsx
index b2eab420..74f5e240 100644
--- a/packages/react/src/auth/forms/sign-up-auth-form.tsx
+++ b/packages/react/src/auth/forms/sign-up-auth-form.tsx
@@ -85,7 +85,7 @@ export function useSignUpAuthForm(onSuccess?: SignUpAuthFormProps["onSignUp"]) {
displayName: requireDisplayName ? "" : undefined,
} as z.infer,
validators: {
- onBlur: schema,
+ onChange: schema,
onSubmitAsync: async ({ value }) => {
try {
const credential = await action(value);
diff --git a/packages/react/src/components/form.tsx b/packages/react/src/components/form.tsx
index 897cf01e..603c8371 100644
--- a/packages/react/src/components/form.tsx
+++ b/packages/react/src/components/form.tsx
@@ -22,15 +22,13 @@ import { cn } from "~/utils/cn";
const { fieldContext, useFieldContext, formContext, useFormContext } = createFormHookContexts();
function FieldMetadata({ className, ...props }: ComponentProps<"div"> & { field: AnyFieldApi }) {
- if (!props.field.state.meta.isTouched || !props.field.state.meta.errors.length) {
+ if (!props.field.state.meta.errors.length) {
return null;
}
return (
-
-
- {props.field.state.meta.errors.map((error) => error.message).join(", ")}
-
+
+ {props.field.state.meta.errors.map((error) => error.message).join(", ")}
);
}
@@ -46,6 +44,7 @@ function Input({
ComponentProps<"input"> & { label: string; before?: ReactNode; action?: ReactNode; description?: ReactNode }
>) {
const field = useFieldContext
();
+ const form = useFormContext();
return (
diff --git a/packages/react/tests/register.integration.test.tsx b/packages/react/tests/register.integration.test.tsx
index f0cd04ad..60f9a9db 100644
--- a/packages/react/tests/register.integration.test.tsx
+++ b/packages/react/tests/register.integration.test.tsx
@@ -43,7 +43,7 @@ import { describe } from "vitest";
describe.skip("TODO");
// describe("Register Integration", () => {
-// // Ensure password is at least 8 characters to pass validation
+// // Ensure password is at least 6 characters to pass validation
// const testPassword = "Test123456!";
// let testEmail: string;
diff --git a/packages/shadcn/src/components/email-link-auth-form.tsx b/packages/shadcn/src/components/email-link-auth-form.tsx
index 748f93eb..dfb5525d 100644
--- a/packages/shadcn/src/components/email-link-auth-form.tsx
+++ b/packages/shadcn/src/components/email-link-auth-form.tsx
@@ -46,6 +46,7 @@ export function EmailLinkAuthForm(props: EmailLinkAuthFormProps) {
const form = useForm({
resolver: standardSchemaResolver(schema),
+ reValidateMode: "onChange",
defaultValues: {
email: "",
},
diff --git a/packages/shadcn/src/components/forgot-password-auth-form.tsx b/packages/shadcn/src/components/forgot-password-auth-form.tsx
index 60546ce1..88219e79 100644
--- a/packages/shadcn/src/components/forgot-password-auth-form.tsx
+++ b/packages/shadcn/src/components/forgot-password-auth-form.tsx
@@ -43,6 +43,7 @@ export function ForgotPasswordAuthForm(props: ForgotPasswordAuthFormProps) {
const form = useForm({
resolver: standardSchemaResolver(schema),
+ reValidateMode: "onChange",
defaultValues: {
email: "",
},
diff --git a/packages/shadcn/src/components/phone-auth-form.tsx b/packages/shadcn/src/components/phone-auth-form.tsx
index 0708e7be..6f2bb29d 100644
--- a/packages/shadcn/src/components/phone-auth-form.tsx
+++ b/packages/shadcn/src/components/phone-auth-form.tsx
@@ -57,6 +57,7 @@ function VerifyPhoneNumberForm(props: VerifyPhoneNumberFormProps) {
const form = useForm({
resolver: standardSchemaResolver(schema),
+ reValidateMode: "onChange",
defaultValues: {
verificationId: props.verificationId,
verificationCode: "",
@@ -122,6 +123,7 @@ function PhoneNumberForm(props: PhoneNumberFormProps) {
const form = useForm({
resolver: standardSchemaResolver(schema),
+ reValidateMode: "onChange",
defaultValues: {
phoneNumber: "",
},
diff --git a/packages/shadcn/src/components/sign-in-auth-form.tsx b/packages/shadcn/src/components/sign-in-auth-form.tsx
index 76c510eb..34438ae6 100644
--- a/packages/shadcn/src/components/sign-in-auth-form.tsx
+++ b/packages/shadcn/src/components/sign-in-auth-form.tsx
@@ -41,6 +41,7 @@ export function SignInAuthForm(props: SignInAuthFormProps) {
const form = useForm({
resolver: standardSchemaResolver(schema),
+ reValidateMode: "onChange",
defaultValues: {
email: "",
password: "",
diff --git a/packages/shadcn/src/components/sign-up-auth-form.tsx b/packages/shadcn/src/components/sign-up-auth-form.tsx
index 43107394..25d8ee56 100644
--- a/packages/shadcn/src/components/sign-up-auth-form.tsx
+++ b/packages/shadcn/src/components/sign-up-auth-form.tsx
@@ -43,6 +43,7 @@ export function SignUpAuthForm(props: SignUpAuthFormProps) {
const form = useForm({
resolver: standardSchemaResolver(schema),
+ reValidateMode: "onChange",
defaultValues: {
email: "",
password: "",
diff --git a/packages/translations/src/locales/en-us.ts b/packages/translations/src/locales/en-us.ts
index 2cb38358..4b49b5d9 100644
--- a/packages/translations/src/locales/en-us.ts
+++ b/packages/translations/src/locales/en-us.ts
@@ -28,7 +28,7 @@ export const enUS = {
missingVerificationCode: "Please enter the verification code",
emailAlreadyInUse: "An account already exists with this email",
invalidCredential: "The provided credentials are invalid.",
- weakPassword: "Password should be at least 8 characters",
+ weakPassword: "Password should be at least 6 characters",
unverifiedEmail: "Please verify your email address to continue.",
operationNotAllowed: "This operation is not allowed. Please contact support.",
invalidPhoneNumber: "The phone number is invalid",