Skip to content

Commit 5a80a9f

Browse files
authored
refactor(react): TanStack Form updates
2 parents 7a0cd76 + 1179ab9 commit 5a80a9f

18 files changed

+2326
-1616
lines changed

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

Lines changed: 228 additions & 214 deletions
Large diffs are not rendered by default.

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

Lines changed: 67 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -16,73 +16,87 @@
1616

1717
"use client";
1818

19-
import {
20-
FirebaseUIError,
21-
completeEmailLinkSignIn,
22-
createEmailLinkAuthFormSchema,
23-
getTranslation,
24-
sendSignInLinkToEmail,
25-
} from "@firebase-ui/core";
26-
import { useForm } from "@tanstack/react-form";
27-
import { useEffect, useMemo, useState } from "react";
28-
import { useUI } from "~/hooks";
29-
import { Button } from "../../components/button";
30-
import { FieldInfo } from "../../components/field-info";
31-
import { Policies } from "../../components/policies";
19+
import { FirebaseUIError, completeEmailLinkSignIn, getTranslation, sendSignInLinkToEmail } from "@firebase-ui/core";
20+
import type { UserCredential } from "firebase/auth";
21+
import { useEmailLinkAuthFormSchema, useUI } from "~/hooks";
22+
import { form } from "~/components/form";
23+
import { Policies } from "~/components/policies";
24+
import { useCallback, useEffect, useState } from "react";
3225

3326
export type EmailLinkAuthFormProps = {
3427
onEmailSent?: () => void;
28+
onSignIn?: (credential: UserCredential) => void;
3529
};
3630

37-
export function EmailLinkAuthForm({ onEmailSent }: EmailLinkAuthFormProps) {
31+
export function useEmailLinkAuthFormAction() {
3832
const ui = useUI();
3933

40-
const [formError, setFormError] = useState<string | null>(null);
41-
const [emailSent, setEmailSent] = useState(false);
42-
const [firstValidationOccured, setFirstValidationOccured] = useState(false);
43-
44-
const emailLinkFormSchema = useMemo(() => createEmailLinkAuthFormSchema(ui), [ui]);
45-
46-
const form = useForm({
47-
defaultValues: {
48-
email: "",
49-
},
50-
validators: {
51-
onBlur: emailLinkFormSchema,
52-
onSubmit: emailLinkFormSchema,
53-
},
54-
onSubmit: async ({ value }) => {
55-
setFormError(null);
34+
return useCallback(
35+
async ({ email }: { email: string }) => {
5636
try {
57-
await sendSignInLinkToEmail(ui, value.email);
58-
setEmailSent(true);
59-
onEmailSent?.();
37+
return await sendSignInLinkToEmail(ui, email);
6038
} catch (error) {
6139
if (error instanceof FirebaseUIError) {
62-
setFormError(error.message);
63-
return;
40+
throw new Error(error.message);
6441
}
6542

6643
console.error(error);
67-
setFormError(getTranslation(ui, "errors", "unknownError"));
44+
throw new Error(getTranslation(ui, "errors", "unknownError"));
6845
}
6946
},
47+
[ui]
48+
);
49+
}
50+
51+
export function useEmailLinkAuthForm(onSuccess?: EmailLinkAuthFormProps["onEmailSent"]) {
52+
const schema = useEmailLinkAuthFormSchema();
53+
const action = useEmailLinkAuthFormAction();
54+
55+
return form.useAppForm({
56+
defaultValues: {
57+
email: "",
58+
},
59+
validators: {
60+
onBlur: schema,
61+
onSubmit: schema,
62+
onSubmitAsync: async ({ value }) => {
63+
try {
64+
await action(value);
65+
onSuccess?.();
66+
} catch (error) {
67+
return error instanceof Error ? error.message : String(error);
68+
}
69+
},
70+
},
7071
});
72+
}
73+
74+
export function useEmailLinkAuthFormCompleteSignIn(onSignIn?: EmailLinkAuthFormProps["onSignIn"]) {
75+
const ui = useUI();
7176

72-
// Handle email link sign-in if URL contains the link
7377
useEffect(() => {
7478
const completeSignIn = async () => {
75-
try {
76-
await completeEmailLinkSignIn(ui, window.location.href);
77-
} catch (error) {
78-
if (error instanceof FirebaseUIError) {
79-
setFormError(error.message);
80-
}
79+
const credential = await completeEmailLinkSignIn(ui, window.location.href);
80+
81+
if (credential) {
82+
onSignIn?.(credential);
8183
}
8284
};
8385

8486
void completeSignIn();
8587
}, [ui]);
88+
}
89+
90+
export function EmailLinkAuthForm({ onEmailSent, onSignIn }: EmailLinkAuthFormProps) {
91+
const ui = useUI();
92+
const [emailSent, setEmailSent] = useState(false);
93+
94+
const form = useEmailLinkAuthForm(() => {
95+
setEmailSent(true);
96+
onEmailSent?.();
97+
});
98+
99+
useEmailLinkAuthFormCompleteSignIn(onSignIn);
86100

87101
if (emailSent) {
88102
return <div className="fui-success">{getTranslation(ui, "messages", "signInLinkSent")}</div>;
@@ -97,47 +111,16 @@ export function EmailLinkAuthForm({ onEmailSent }: EmailLinkAuthFormProps) {
97111
await form.handleSubmit();
98112
}}
99113
>
100-
<fieldset>
101-
<form.Field
102-
name="email"
103-
// eslint-disable-next-line react/no-children-prop
104-
children={(field) => (
105-
<>
106-
<label htmlFor={field.name}>
107-
<span>{getTranslation(ui, "labels", "emailAddress")}</span>
108-
<input
109-
aria-invalid={field.state.meta.isTouched && field.state.meta.errors.length > 0}
110-
id={field.name}
111-
name={field.name}
112-
type="email"
113-
value={field.state.value}
114-
onBlur={() => {
115-
setFirstValidationOccured(true);
116-
field.handleBlur();
117-
}}
118-
onInput={(e) => {
119-
field.handleChange((e.target as HTMLInputElement).value);
120-
if (firstValidationOccured) {
121-
field.handleBlur();
122-
form.update();
123-
}
124-
}}
125-
/>
126-
<FieldInfo field={field} />
127-
</label>
128-
</>
129-
)}
130-
/>
131-
</fieldset>
132-
133-
<Policies />
134-
135-
<fieldset>
136-
<Button type="submit" disabled={ui.state !== "idle"}>
137-
{getTranslation(ui, "labels", "sendSignInLink")}
138-
</Button>
139-
{formError && <div className="fui-form__error">{formError}</div>}
140-
</fieldset>
114+
<form.AppForm>
115+
<fieldset>
116+
<form.AppField name="email" children={(field) => <field.Input label="Email" type="email" />} />
117+
</fieldset>
118+
<Policies />
119+
<fieldset>
120+
<form.SubmitButton>{getTranslation(ui, "labels", "sendSignInLink")}</form.SubmitButton>
121+
<form.ErrorMessage />
122+
</fieldset>
123+
</form.AppForm>
141124
</form>
142125
);
143126
}

0 commit comments

Comments
 (0)