fix(login): use onChange instead of onBlur with react-hook-form (#11354)

# Which Problems Are Solved

Fixes a problem where submit buttons where not correctly enabled onBlur
event

# How the Problems Are Solved

By changing the react-hook-form mode property to onChange, buttons are
enabled as intended
This commit is contained in:
Max Peintner
2026-01-14 08:07:04 +00:00
committed by GitHub
parent 86fd769439
commit cd36abd725
13 changed files with 38 additions and 131 deletions
@@ -1,15 +1,7 @@
"use client";
import {
lowerCaseValidator,
numberValidator,
symbolValidator,
upperCaseValidator,
} from "@/helpers/validators";
import {
checkSessionAndSetPassword,
sendPassword,
} from "@/lib/server/password";
import { lowerCaseValidator, numberValidator, symbolValidator, upperCaseValidator } from "@/helpers/validators";
import { checkSessionAndSetPassword, sendPassword } from "@/lib/server/password";
import { create } from "@zitadel/client";
import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb";
@@ -40,17 +32,11 @@ type Props = {
organization?: string;
};
export function ChangePasswordForm({
passwordComplexitySettings,
sessionId,
loginName,
requestId,
organization,
}: Props) {
export function ChangePasswordForm({ passwordComplexitySettings, sessionId, loginName, requestId, organization }: Props) {
const router = useRouter();
const { register, handleSubmit, watch, formState } = useForm<Inputs>({
mode: "onBlur",
mode: "onChange",
defaultValues: {
password: "",
comfirmPassword: "",
@@ -78,11 +64,7 @@ export function ChangePasswordForm({
});
if (changeResponse && "error" in changeResponse && changeResponse.error) {
setError(
typeof changeResponse.error === "string"
? changeResponse.error
: t("change.errors.unknownError")
);
setError(typeof changeResponse.error === "string" ? changeResponse.error : t("change.errors.unknownError"));
return;
}
@@ -109,20 +91,12 @@ export function ChangePasswordForm({
setLoading(false);
});
if (
passwordResponse &&
"error" in passwordResponse &&
passwordResponse.error
) {
if (passwordResponse && "error" in passwordResponse && passwordResponse.error) {
setError(passwordResponse.error);
return;
}
if (
passwordResponse &&
"redirect" in passwordResponse &&
passwordResponse.redirect
) {
if (passwordResponse && "redirect" in passwordResponse && passwordResponse.redirect) {
return router.push(passwordResponse.redirect);
}
@@ -134,9 +108,7 @@ export function ChangePasswordForm({
const watchPassword = watch("password", "");
const watchConfirmPassword = watch("confirmPassword", "");
const hasMinLength =
passwordComplexitySettings &&
watchPassword?.length >= passwordComplexitySettings.minLength;
const hasMinLength = passwordComplexitySettings && watchPassword?.length >= passwordComplexitySettings.minLength;
const hasSymbol = symbolValidator(watchPassword);
const hasNumber = numberValidator(watchPassword);
const hasUppercase = upperCaseValidator(watchPassword);
@@ -196,17 +168,11 @@ export function ChangePasswordForm({
<Button
type="submit"
variant={ButtonVariants.Primary}
disabled={
loading ||
!policyIsValid ||
!formState.isValid ||
watchPassword !== watchConfirmPassword
}
disabled={loading || !policyIsValid || !formState.isValid || watchPassword !== watchConfirmPassword}
onClick={handleSubmit(submitChange)}
data-testid="submit-button"
>
{loading && <Spinner className="mr-2 h-5 w-5" />}{" "}
<Translated i18nKey="change.submit" namespace="password" />
{loading && <Spinner className="mr-2 h-5 w-5" />} <Translated i18nKey="change.submit" namespace="password" />
</Button>
</div>
</form>
@@ -20,7 +20,7 @@ export function DeviceCodeForm({ userCode }: { userCode?: string }) {
const router = useRouter();
const { register, handleSubmit, formState } = useForm<Inputs>({
mode: "onBlur",
mode: "onChange",
defaultValues: {
userCode: userCode || "",
},
@@ -88,8 +88,7 @@ export function DeviceCodeForm({ userCode }: { userCode?: string }) {
onClick={handleSubmit(submitCodeAndContinue)}
data-testid="submit-button"
>
{loading && <Spinner className="mr-2 h-5 w-5" />}{" "}
<Translated i18nKey="usercode.submit" namespace="device" />
{loading && <Spinner className="mr-2 h-5 w-5" />} <Translated i18nKey="usercode.submit" namespace="device" />
</Button>
</div>
</form>
@@ -24,7 +24,7 @@ type Props = {
export function LDAPUsernamePasswordForm({ idpId, link }: Props) {
const { register, handleSubmit, formState } = useForm<Inputs>({
mode: "onBlur",
mode: "onChange",
});
const t = useTranslations("ldap");
+1 -1
View File
@@ -44,7 +44,7 @@ export function LoginOTP({ host, loginName, sessionId, requestId, organization,
const initialized = useRef(false);
const { register, handleSubmit, formState } = useForm<Inputs>({
mode: "onBlur",
mode: "onChange",
defaultValues: {
code: code ? code : "",
},
+1 -1
View File
@@ -28,7 +28,7 @@ type Props = {
export function PasswordForm({ loginSettings, loginName, organization, requestId }: Props) {
const { register, handleSubmit, formState } = useForm<Inputs>({
mode: "onBlur",
mode: "onChange",
});
const t = useTranslations("password");
@@ -47,7 +47,7 @@ export function RegisterFormIDPIncomplete({
idpUserName,
}: Props) {
const { register, handleSubmit, formState } = useForm<Inputs>({
mode: "onBlur",
mode: "onChange",
defaultValues: {
email: defaultValues?.email ?? "",
firstname: defaultValues?.firstname ?? "",
+1 -1
View File
@@ -46,7 +46,7 @@ export function RegisterForm({
idpCount = 0,
}: Props) {
const { register, handleSubmit, formState } = useForm<Inputs>({
mode: "onBlur",
mode: "onChange",
defaultValues: {
email: email ?? "",
firstName: firstname ?? "",
@@ -25,7 +25,7 @@ type Props = {
export function RegisterPasskey({ sessionId, userId, isPrompt, organization, requestId, code, codeId }: Props) {
const { handleSubmit, formState } = useForm<Inputs>({
mode: "onBlur",
mode: "onChange",
});
const [error, setError] = useState<string>("");
@@ -1,16 +1,7 @@
"use client";
import {
lowerCaseValidator,
numberValidator,
symbolValidator,
upperCaseValidator,
} from "@/helpers/validators";
import {
changePassword,
resetPassword,
sendPassword,
} from "@/lib/server/password";
import { lowerCaseValidator, numberValidator, symbolValidator, upperCaseValidator } from "@/helpers/validators";
import { changePassword, resetPassword, sendPassword } from "@/lib/server/password";
import { create } from "@zitadel/client";
import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb";
@@ -54,7 +45,7 @@ export function SetPasswordForm({
codeRequired,
}: Props) {
const { register, handleSubmit, watch, formState } = useForm<Inputs>({
mode: "onBlur",
mode: "onChange",
defaultValues: {
code: code ?? "",
},
@@ -148,20 +139,12 @@ export function SetPasswordForm({
setLoading(false);
});
if (
passwordResponse &&
"error" in passwordResponse &&
passwordResponse.error
) {
if (passwordResponse && "error" in passwordResponse && passwordResponse.error) {
setError(passwordResponse.error);
return;
}
if (
passwordResponse &&
"redirect" in passwordResponse &&
passwordResponse.redirect
) {
if (passwordResponse && "redirect" in passwordResponse && passwordResponse.redirect) {
return router.push(passwordResponse.redirect);
}
@@ -173,9 +156,7 @@ export function SetPasswordForm({
const watchPassword = watch("password", "");
const watchConfirmPassword = watch("confirmPassword", "");
const hasMinLength =
passwordComplexitySettings &&
watchPassword?.length >= passwordComplexitySettings.minLength;
const hasMinLength = passwordComplexitySettings && watchPassword?.length >= passwordComplexitySettings.minLength;
const hasSymbol = symbolValidator(watchPassword);
const hasNumber = numberValidator(watchPassword);
const hasUppercase = upperCaseValidator(watchPassword);
@@ -271,17 +252,11 @@ export function SetPasswordForm({
<Button
type="submit"
variant={ButtonVariants.Primary}
disabled={
loading ||
!policyIsValid ||
!formState.isValid ||
watchPassword !== watchConfirmPassword
}
disabled={loading || !policyIsValid || !formState.isValid || watchPassword !== watchConfirmPassword}
onClick={handleSubmit(submitPassword)}
data-testid="submit-button"
>
{loading && <Spinner className="mr-2 h-5 w-5" />}{" "}
<Translated i18nKey="set.submit" namespace="password" />
{loading && <Spinner className="mr-2 h-5 w-5" />} <Translated i18nKey="set.submit" namespace="password" />
</Button>
</div>
</form>
@@ -1,11 +1,6 @@
"use client";
import {
lowerCaseValidator,
numberValidator,
symbolValidator,
upperCaseValidator,
} from "@/helpers/validators";
import { lowerCaseValidator, numberValidator, symbolValidator, upperCaseValidator } from "@/helpers/validators";
import { registerUser } from "@/lib/server/register";
import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb";
import { useRouter } from "next/navigation";
@@ -45,7 +40,7 @@ export function SetRegisterPasswordForm({
requestId,
}: Props) {
const { register, handleSubmit, watch, formState } = useForm<Inputs>({
mode: "onBlur",
mode: "onChange",
defaultValues: {
email: email ?? "",
firstname: firstname ?? "",
@@ -93,9 +88,7 @@ export function SetRegisterPasswordForm({
const watchPassword = watch("password", "");
const watchConfirmPassword = watch("confirmPassword", "");
const hasMinLength =
passwordComplexitySettings &&
watchPassword?.length >= passwordComplexitySettings.minLength;
const hasMinLength = passwordComplexitySettings && watchPassword?.length >= passwordComplexitySettings.minLength;
const hasSymbol = symbolValidator(watchPassword);
const hasNumber = numberValidator(watchPassword);
const hasUppercase = upperCaseValidator(watchPassword);
@@ -155,17 +148,11 @@ export function SetRegisterPasswordForm({
<Button
type="submit"
variant={ButtonVariants.Primary}
disabled={
loading ||
!policyIsValid ||
!formState.isValid ||
watchPassword !== watchConfirmPassword
}
disabled={loading || !policyIsValid || !formState.isValid || watchPassword !== watchConfirmPassword}
onClick={handleSubmit(submitRegister)}
data-testid="submit-button"
>
{loading && <Spinner className="mr-2 h-5 w-5" />}{" "}
<Translated i18nKey="password.submit" namespace="register" />
{loading && <Spinner className="mr-2 h-5 w-5" />} <Translated i18nKey="password.submit" namespace="register" />
</Button>
</div>
</form>
+1 -1
View File
@@ -36,7 +36,7 @@ export function TotpRegister({ uri, loginName, sessionId, requestId, organizatio
const router = useRouter();
const { register, handleSubmit, formState } = useForm<Inputs>({
mode: "onBlur",
mode: "onChange",
defaultValues: {
code: "",
},
+3 -14
View File
@@ -27,17 +27,9 @@ type Props = {
allowRegister: boolean;
};
export function UsernameForm({
loginName,
requestId,
organization,
suffix,
loginSettings,
submit,
allowRegister,
}: Props) {
export function UsernameForm({ loginName, requestId, organization, suffix, loginSettings, submit, allowRegister }: Props) {
const { register, handleSubmit, formState } = useForm<Inputs>({
mode: "onBlur",
mode: "onChange",
defaultValues: {
loginName: loginName ? loginName : "",
},
@@ -87,10 +79,7 @@ export function UsernameForm({
}, []);
let inputLabel = t("labels.loginname");
if (
loginSettings?.disableLoginWithEmail &&
loginSettings?.disableLoginWithPhone
) {
if (loginSettings?.disableLoginWithEmail && loginSettings?.disableLoginWithPhone) {
inputLabel = t("labels.username");
} else if (loginSettings?.disableLoginWithEmail) {
inputLabel = t("labels.usernameOrPhoneNumber");
+3 -12
View File
@@ -25,18 +25,11 @@ type Props = {
requestId?: string;
};
export function VerifyForm({
userId,
loginName,
organization,
requestId,
code,
isInvite,
}: Props) {
export function VerifyForm({ userId, loginName, organization, requestId, code, isInvite }: Props) {
const router = useRouter();
const { register, handleSubmit, formState } = useForm<Inputs>({
mode: "onBlur",
mode: "onChange",
defaultValues: {
code: code ?? "",
},
@@ -73,9 +66,7 @@ export function VerifyForm({
}
const fcn = useCallback(
async function submitCodeAndContinue(
value: Inputs,
): Promise<boolean | void> {
async function submitCodeAndContinue(value: Inputs): Promise<boolean | void> {
setLoading(true);
const response = await sendVerification({