Skip to content

Commit 9c35ebf

Browse files
authored
fix: Validation form fields onInput after first validation
2 parents bccf478 + a1eea57 commit 9c35ebf

File tree

10 files changed

+264
-16
lines changed

10 files changed

+264
-16
lines changed

packages/firebaseui-react/src/auth/forms/email-link-form.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export function EmailLinkForm() {
2424
const translations = useTranslations();
2525
const [formError, setFormError] = useState<string | null>(null);
2626
const [emailSent, setEmailSent] = useState(false);
27+
const [firstValidationOccured, setFirstValidationOccured] = useState(false);
2728

2829
const emailLinkFormSchema = useMemo(
2930
() => createEmailLinkFormSchema(translations),
@@ -128,8 +129,17 @@ export function EmailLinkForm() {
128129
name={field.name}
129130
type="email"
130131
value={field.state.value}
131-
onBlur={field.handleBlur}
132-
onChange={(e) => field.handleChange(e.target.value)}
132+
onBlur={() => {
133+
setFirstValidationOccured(true);
134+
field.handleBlur();
135+
}}
136+
onInput={(e) => {
137+
field.handleChange((e.target as HTMLInputElement).value);
138+
if (firstValidationOccured) {
139+
field.handleBlur();
140+
form.update();
141+
}
142+
}}
133143
/>
134144
<FieldInfo field={field} />
135145
</label>

packages/firebaseui-react/src/auth/forms/email-password-form.tsx

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export function EmailPasswordForm({
3131
enableHandleExistingCredential,
3232
} = useConfig();
3333
const [formError, setFormError] = useState<string | null>(null);
34+
const [firstValidationOccured, setFirstValidationOccured] = useState(false);
3435

3536
// TODO: Do we need to memoize this?
3637
const emailFormSchema = useMemo(
@@ -102,8 +103,17 @@ export function EmailPasswordForm({
102103
name={field.name}
103104
type="email"
104105
value={field.state.value}
105-
onBlur={field.handleBlur}
106-
onChange={(e) => field.handleChange(e.target.value)}
106+
onBlur={() => {
107+
setFirstValidationOccured(true);
108+
field.handleBlur();
109+
}}
110+
onInput={(e) => {
111+
field.handleChange((e.target as HTMLInputElement).value);
112+
if (firstValidationOccured) {
113+
field.handleBlur();
114+
form.update();
115+
}
116+
}}
107117
/>
108118
<FieldInfo field={field} />
109119
</label>
@@ -149,8 +159,17 @@ export function EmailPasswordForm({
149159
name={field.name}
150160
type="password"
151161
value={field.state.value}
152-
onBlur={field.handleBlur}
153-
onChange={(e) => field.handleChange(e.target.value)}
162+
onBlur={() => {
163+
setFirstValidationOccured(true);
164+
field.handleBlur();
165+
}}
166+
onInput={(e) => {
167+
field.handleChange((e.target as HTMLInputElement).value);
168+
if (firstValidationOccured) {
169+
field.handleBlur();
170+
form.update();
171+
}
172+
}}
154173
/>
155174
<FieldInfo field={field} />
156175
</label>

packages/firebaseui-react/src/auth/forms/forgot-password-form.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export function ForgotPasswordForm({
2626
const { language } = useConfig();
2727
const [formError, setFormError] = useState<string | null>(null);
2828
const [emailSent, setEmailSent] = useState(false);
29+
const [firstValidationOccured, setFirstValidationOccured] = useState(false);
2930
const forgotPasswordFormSchema = useMemo(
3031
() => createForgotPasswordFormSchema(translations),
3132
[translations]
@@ -106,8 +107,17 @@ export function ForgotPasswordForm({
106107
name={field.name}
107108
type="email"
108109
value={field.state.value}
109-
onBlur={field.handleBlur}
110-
onChange={(e) => field.handleChange(e.target.value)}
110+
onBlur={() => {
111+
setFirstValidationOccured(true);
112+
field.handleBlur();
113+
}}
114+
onInput={(e) => {
115+
field.handleChange((e.target as HTMLInputElement).value);
116+
if (firstValidationOccured) {
117+
field.handleBlur();
118+
form.update();
119+
}
120+
}}
111121
/>
112122
<FieldInfo field={field} />
113123
</label>

packages/firebaseui-react/src/auth/forms/phone-form.tsx

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ function PhoneNumberForm({
4040
const [selectedCountry, setSelectedCountry] = useState<CountryData>(
4141
countryData[0]
4242
);
43+
const [firstValidationOccured, setFirstValidationOccured] = useState(false);
4344

4445
const phoneFormSchema = useMemo(
4546
() =>
@@ -104,8 +105,17 @@ function PhoneNumberForm({
104105
name={field.name}
105106
type="tel"
106107
value={field.state.value}
107-
onBlur={field.handleBlur}
108-
onChange={(e) => field.handleChange(e.target.value)}
108+
onBlur={() => {
109+
setFirstValidationOccured(true);
110+
field.handleBlur();
111+
}}
112+
onInput={(e) => {
113+
field.handleChange((e.target as HTMLInputElement).value);
114+
if (firstValidationOccured) {
115+
field.handleBlur();
116+
phoneForm.update();
117+
}
118+
}}
109119
className="fui-phone-input__number-input"
110120
/>
111121
</div>
@@ -195,6 +205,7 @@ function VerificationForm({
195205
}: VerificationFormProps) {
196206
const { language } = useConfig();
197207
const translations = useTranslations();
208+
const [firstValidationOccured, setFirstValidationOccured] = useState(false);
198209

199210
const verificationFormSchema = useMemo(
200211
() =>
@@ -249,8 +260,17 @@ function VerificationForm({
249260
name={field.name}
250261
type="text"
251262
value={field.state.value}
252-
onBlur={field.handleBlur}
253-
onChange={(e) => field.handleChange(e.target.value)}
263+
onBlur={() => {
264+
setFirstValidationOccured(true);
265+
field.handleBlur();
266+
}}
267+
onInput={(e) => {
268+
field.handleChange((e.target as HTMLInputElement).value);
269+
if (firstValidationOccured) {
270+
field.handleBlur();
271+
verificationForm.update();
272+
}
273+
}}
254274
/>
255275
<FieldInfo field={field} />
256276
</label>

packages/firebaseui-react/src/auth/forms/register-form.tsx

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export function RegisterForm({
2323
const { language, enableAutoUpgradeAnonymous } = useConfig();
2424
const translations = useTranslations();
2525
const [formError, setFormError] = useState<string | null>(null);
26+
const [firstValidationOccured, setFirstValidationOccured] = useState(false);
2627
const emailFormSchema = useMemo(
2728
() => createEmailFormSchema(translations),
2829
[translations]
@@ -96,8 +97,17 @@ export function RegisterForm({
9697
name={field.name}
9798
type="email"
9899
value={field.state.value}
99-
onBlur={field.handleBlur}
100-
onChange={(e) => field.handleChange(e.target.value)}
100+
onBlur={() => {
101+
setFirstValidationOccured(true);
102+
field.handleBlur();
103+
}}
104+
onInput={(e) => {
105+
field.handleChange((e.target as HTMLInputElement).value);
106+
if (firstValidationOccured) {
107+
field.handleBlur();
108+
form.update();
109+
}
110+
}}
101111
/>
102112
<FieldInfo field={field} />
103113
</label>
@@ -124,8 +134,17 @@ export function RegisterForm({
124134
name={field.name}
125135
type="password"
126136
value={field.state.value}
127-
onBlur={field.handleBlur}
128-
onChange={(e) => field.handleChange(e.target.value)}
137+
onBlur={() => {
138+
setFirstValidationOccured(true);
139+
field.handleBlur();
140+
}}
141+
onInput={(e) => {
142+
field.handleChange((e.target as HTMLInputElement).value);
143+
if (firstValidationOccured) {
144+
field.handleBlur();
145+
form.update();
146+
}
147+
}}
129148
/>
130149
<FieldInfo field={field} />
131150
</label>

packages/firebaseui-react/tests/unit/auth/forms/email-link-form.test.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,4 +292,36 @@ describe("EmailLinkForm", () => {
292292
// Verify that the success state was updated
293293
expect(setEmailSentMock).toHaveBeenCalledWith(true);
294294
});
295+
296+
it("validates on blur for the first time", async () => {
297+
render(<EmailLinkForm />);
298+
299+
const emailInput = screen.getByLabelText("Email");
300+
301+
await act(async () => {
302+
fireEvent.blur(emailInput);
303+
});
304+
305+
// Check that form validation is available
306+
expect((global as any).formOnSubmit).toBeDefined();
307+
});
308+
309+
it("validates on input after first blur", async () => {
310+
render(<EmailLinkForm />);
311+
312+
const emailInput = screen.getByLabelText("Email");
313+
314+
// First validation on blur
315+
await act(async () => {
316+
fireEvent.blur(emailInput);
317+
});
318+
319+
// Then validation should happen on input
320+
await act(async () => {
321+
fireEvent.input(emailInput, { target: { value: "[email protected]" } });
322+
});
323+
324+
// Check that form validation is available
325+
expect((global as any).formOnSubmit).toBeDefined();
326+
});
295327
});

packages/firebaseui-react/tests/unit/auth/forms/email-password-form.test.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,4 +189,41 @@ describe("EmailPasswordForm", () => {
189189
// Check that the authentication function was called
190190
expect(fuiSignInWithEmailAndPassword).toHaveBeenCalled();
191191
});
192+
193+
it("validates on blur for the first time", async () => {
194+
render(<EmailPasswordForm />);
195+
196+
const emailInput = screen.getByRole("textbox", { name: /email address/i });
197+
const passwordInput = screen.getByDisplayValue("password123");
198+
199+
await act(async () => {
200+
fireEvent.blur(emailInput);
201+
fireEvent.blur(passwordInput);
202+
});
203+
204+
// Check that handleBlur was called
205+
expect((global as any).formOnSubmit).toBeDefined();
206+
});
207+
208+
it("validates on input after first blur", async () => {
209+
render(<EmailPasswordForm />);
210+
211+
const emailInput = screen.getByRole("textbox", { name: /email address/i });
212+
const passwordInput = screen.getByDisplayValue("password123");
213+
214+
// First validation on blur
215+
await act(async () => {
216+
fireEvent.blur(emailInput);
217+
fireEvent.blur(passwordInput);
218+
});
219+
220+
// Then validation should happen on input
221+
await act(async () => {
222+
fireEvent.input(emailInput, { target: { value: "[email protected]" } });
223+
fireEvent.input(passwordInput, { target: { value: "password123" } });
224+
});
225+
226+
// Check that handleBlur and form.update were called
227+
expect((global as any).formOnSubmit).toBeDefined();
228+
});
192229
});

packages/firebaseui-react/tests/unit/auth/forms/forgot-password-form.test.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,38 @@ describe("ForgotPasswordForm", () => {
185185
expect(fuiSendPasswordResetEmail).toHaveBeenCalled();
186186
});
187187

188+
it("validates on blur for the first time", async () => {
189+
render(<ForgotPasswordForm />);
190+
191+
const emailInput = screen.getByRole("textbox", { name: /email address/i });
192+
193+
await act(async () => {
194+
fireEvent.blur(emailInput);
195+
});
196+
197+
// Check that handleBlur was called
198+
expect((global as any).formOnSubmit).toBeDefined();
199+
});
200+
201+
it("validates on input after first blur", async () => {
202+
render(<ForgotPasswordForm />);
203+
204+
const emailInput = screen.getByRole("textbox", { name: /email address/i });
205+
206+
// First validation on blur
207+
await act(async () => {
208+
fireEvent.blur(emailInput);
209+
});
210+
211+
// Then validation should happen on input
212+
await act(async () => {
213+
fireEvent.input(emailInput, { target: { value: "[email protected]" } });
214+
});
215+
216+
// Check that handleBlur and form.update were called
217+
expect((global as any).formOnSubmit).toBeDefined();
218+
});
219+
188220
it("displays back to sign in button when provided", () => {
189221
const onBackToSignInClickMock = vi.fn();
190222
render(

packages/firebaseui-react/tests/unit/auth/forms/phone-form.test.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,4 +253,36 @@ describe("PhoneForm", () => {
253253
// The UI should show the error message in the form__error div
254254
expect(screen.getByText("An unknown error occurred")).toBeInTheDocument();
255255
});
256+
257+
it("validates on blur for the first time", async () => {
258+
render(<PhoneForm />);
259+
260+
const phoneInput = screen.getByRole("textbox", { name: /phone number/i });
261+
262+
await act(async () => {
263+
fireEvent.blur(phoneInput);
264+
});
265+
266+
// Check that handleBlur was called
267+
expect((global as any).formOnSubmit).toBeDefined();
268+
});
269+
270+
it("validates on input after first blur", async () => {
271+
render(<PhoneForm />);
272+
273+
const phoneInput = screen.getByRole("textbox", { name: /phone number/i });
274+
275+
// First validation on blur
276+
await act(async () => {
277+
fireEvent.blur(phoneInput);
278+
});
279+
280+
// Then validation should happen on input
281+
await act(async () => {
282+
fireEvent.input(phoneInput, { target: { value: "1234567890" } });
283+
});
284+
285+
// Check that handleBlur and form.update were called
286+
expect((global as any).formOnSubmit).toBeDefined();
287+
});
256288
});

0 commit comments

Comments
 (0)