Skip to content

Commit ccb705a

Browse files
committed
refactor(auth): simplify loading state propagation
Replace effect-based sync with direct inline propagation. Children notify parent via callback when setting loading state. Parent's setChildLoading is now idempotent to handle redundant calls gracefully.
1 parent e465a5e commit ccb705a

File tree

4 files changed

+38
-30
lines changed

4 files changed

+38
-30
lines changed

apps/app/components/auth/otp-verification.tsx

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,13 @@ export function OtpVerification({
2626
const [isLoading, setIsLoading] = useState(false);
2727
const [resendCooldown, setResendCooldown] = useState(0);
2828

29-
// Sync loading state to parent
30-
useEffect(() => {
31-
onLoadingChange?.(isLoading);
32-
}, [isLoading, onLoadingChange]);
29+
const setLoading = useCallback(
30+
(loading: boolean) => {
31+
setIsLoading(loading);
32+
onLoadingChange?.(loading);
33+
},
34+
[onLoadingChange],
35+
);
3336

3437
// Countdown timer for resend cooldown
3538
useEffect(() => {
@@ -45,7 +48,7 @@ export function OtpVerification({
4548
if (!email || !otp) return;
4649

4750
try {
48-
setIsLoading(true);
51+
setLoading(true);
4952
onError(null);
5053

5154
const result = await auth.signIn.emailOtp({
@@ -71,7 +74,7 @@ export function OtpVerification({
7174
console.error("OTP verification error:", err);
7275
onError("Failed to verify code");
7376
} finally {
74-
setIsLoading(false);
77+
setLoading(false);
7578
}
7679
};
7780

@@ -82,7 +85,7 @@ export function OtpVerification({
8285
onError(null);
8386

8487
try {
85-
setIsLoading(true);
88+
setLoading(true);
8689

8790
// "sign-in" type handles both login and signup (creates user if needed)
8891
const result = await auth.emailOtp.sendVerificationOtp({
@@ -99,9 +102,9 @@ export function OtpVerification({
99102
console.error("Email OTP error:", err);
100103
onError("Failed to send verification code");
101104
} finally {
102-
setIsLoading(false);
105+
setLoading(false);
103106
}
104-
}, [email, onError, resendCooldown]);
107+
}, [email, onError, resendCooldown, setLoading]);
105108

106109
const disabled = isDisabled || isLoading;
107110

apps/app/components/auth/passkey-login.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { auth } from "@/lib/auth";
22
import { authConfig } from "@/lib/auth-config";
33
import { Button } from "@repo/ui";
44
import { KeyRound } from "lucide-react";
5-
import { useEffect, useRef, useState } from "react";
5+
import { useCallback, useEffect, useRef, useState } from "react";
66

77
interface PasskeyLoginProps {
88
onSuccess: () => void;
@@ -25,10 +25,13 @@ export function PasskeyLogin({
2525
}: PasskeyLoginProps) {
2626
const [isLoading, setIsLoading] = useState(false);
2727

28-
// Sync loading state to parent
29-
useEffect(() => {
30-
onLoadingChange?.(isLoading);
31-
}, [isLoading, onLoadingChange]);
28+
const setLoading = useCallback(
29+
(loading: boolean) => {
30+
setIsLoading(loading);
31+
onLoadingChange?.(loading);
32+
},
33+
[onLoadingChange],
34+
);
3235
const onSuccessRef = useRef(onSuccess);
3336
onSuccessRef.current = onSuccess;
3437

@@ -71,7 +74,7 @@ export function PasskeyLogin({
7174
return;
7275
}
7376

74-
setIsLoading(true);
77+
setLoading(true);
7578
onError(null);
7679

7780
try {
@@ -96,7 +99,7 @@ export function PasskeyLogin({
9699
// Network-level failures (offline, DNS, connection refused)
97100
onError(authConfig.errors.networkError);
98101
} finally {
99-
setIsLoading(false);
102+
setLoading(false);
100103
}
101104
};
102105

apps/app/components/auth/social-login.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { auth } from "@/lib/auth";
22
import { sessionQueryKey } from "@/lib/queries/session";
33
import { Button } from "@repo/ui";
44
import { useQueryClient } from "@tanstack/react-query";
5-
import { useEffect, useState } from "react";
5+
import { useCallback, useState } from "react";
66

77
interface SocialLoginProps {
88
onError: (error: string | null) => void;
@@ -21,13 +21,16 @@ export function SocialLogin({
2121
const queryClient = useQueryClient();
2222
const [isLoading, setIsLoading] = useState(false);
2323

24-
// Sync loading state to parent
25-
useEffect(() => {
26-
onLoadingChange?.(isLoading);
27-
}, [isLoading, onLoadingChange]);
24+
const setLoading = useCallback(
25+
(loading: boolean) => {
26+
setIsLoading(loading);
27+
onLoadingChange?.(loading);
28+
},
29+
[onLoadingChange],
30+
);
2831

2932
const handleGoogleLogin = async () => {
30-
setIsLoading(true);
33+
setLoading(true);
3134
onError(null);
3235

3336
try {
@@ -47,13 +50,13 @@ export function SocialLogin({
4750
// Handle error result (Better Auth returns { error } instead of throwing)
4851
if (result?.error) {
4952
onError(result.error.message || "Failed to sign in with Google");
50-
setIsLoading(false);
53+
setLoading(false);
5154
}
5255
// On success, page redirects - component unmounts, no cleanup needed
5356
} catch (err) {
5457
console.error("Google login error:", err);
5558
onError("Failed to sign in with Google");
56-
setIsLoading(false);
59+
setLoading(false);
5760
}
5861
};
5962

apps/app/components/auth/use-auth-form.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,14 @@ export function useAuthForm({
5353
// Unified busy state: parent loading, child loading, or external loading
5454
const isDisabled = isLoading || isChildLoading || !!isExternallyLoading;
5555

56-
// Keyed loading tracker - prevents race when multiple children are mounted
56+
// Keyed loading tracker - idempotent to handle redundant calls from children
5757
const setChildLoading = useCallback((key: AuthChildKey, loading: boolean) => {
5858
setLoadingChildren((prev) => {
59+
const has = prev.has(key);
60+
if (loading ? has : !has) return prev; // No logical change
5961
const next = new Set(prev);
60-
if (loading) {
61-
next.add(key);
62-
} else {
63-
next.delete(key);
64-
}
62+
if (loading) next.add(key);
63+
else next.delete(key);
6564
return next;
6665
});
6766
}, []);

0 commit comments

Comments
 (0)