Olamide
Olamide4mo ago

Seems like convex auth isn't setting user immediately

for some reason so if I redirect to a page that checks if there is a valid user if there isn't it redirects me back to sign in so now immediately I redirect to "/dashboard" it redirects me back to "/sign-in" My code for getting currently logged in user
import { query } from './_generated/server';
import { getAuthUserId } from '@convex-dev/auth/server';

export const currentUser = query({
args: {},
handler: async (ctx) => {
const userId = await getAuthUserId(ctx);

if (userId === null) {
return null;
}

return await ctx.db.get(userId);
},
});
import { query } from './_generated/server';
import { getAuthUserId } from '@convex-dev/auth/server';

export const currentUser = query({
args: {},
handler: async (ctx) => {
const userId = await getAuthUserId(ctx);

if (userId === null) {
return null;
}

return await ctx.db.get(userId);
},
});
The Component
import { api } from '#convex/_generated/api';
import { convexQuery } from '@convex-dev/react-query';
import { useQuery } from '@tanstack/react-query';
import { createFileRoute, Outlet } from '@tanstack/react-router';
import { useConvexAuth } from 'convex/react';
import { useEffect } from 'react';

export const Route = createFileRoute('/_auth')({
component: AuthLayout,
});

function AuthLayout() {
const { isAuthenticated, isLoading } = useConvexAuth();
const navigate = Route.useNavigate();
const { data, isPending, error } = useQuery(
convexQuery(api.users.currentUser, {}),
);

useEffect(() => {
if (!isPending && !error) {
if (data === null) {
navigate({ to: '/sign-in' });
} else if (!data.isOnboardingComplete) {
navigate({ to: '/onboarding' });
}
}
}, [isPending, error, data, navigate]);

useEffect(() => {
if (!isLoading && !isAuthenticated) {
navigate({
to: '/sign-in',
});
}
}, [isAuthenticated, isLoading, navigate]);

return (
<div>
<Outlet />
</div>
);
}
import { api } from '#convex/_generated/api';
import { convexQuery } from '@convex-dev/react-query';
import { useQuery } from '@tanstack/react-query';
import { createFileRoute, Outlet } from '@tanstack/react-router';
import { useConvexAuth } from 'convex/react';
import { useEffect } from 'react';

export const Route = createFileRoute('/_auth')({
component: AuthLayout,
});

function AuthLayout() {
const { isAuthenticated, isLoading } = useConvexAuth();
const navigate = Route.useNavigate();
const { data, isPending, error } = useQuery(
convexQuery(api.users.currentUser, {}),
);

useEffect(() => {
if (!isPending && !error) {
if (data === null) {
navigate({ to: '/sign-in' });
} else if (!data.isOnboardingComplete) {
navigate({ to: '/onboarding' });
}
}
}, [isPending, error, data, navigate]);

useEffect(() => {
if (!isLoading && !isAuthenticated) {
navigate({
to: '/sign-in',
});
}
}, [isAuthenticated, isLoading, navigate]);

return (
<div>
<Outlet />
</div>
);
}
8 Replies
erquhart
erquhart4mo ago
In the client you're calling the query right away whether the user is authenticated or not. Pass "skip" to the query instead of an object until the user is authenticated in the client.
useQuery(convexQuery(api.users.currentUser, isAuthenticated ? {} : 'skip'));
useQuery(convexQuery(api.users.currentUser, isAuthenticated ? {} : 'skip'));
Olamide
OlamideOP4mo ago
It still doesn't fix it isAuthenticated should be true immediately I sign up right I logged it, it is false so it redirects me back to sign in isLoading is false while isAuthenticated also is still false
sshader
sshader4mo ago
isAuthenticated should be true immediately (after) I sign up right
This isn't true -- isAuthenticated is true once signUp / singIn has finished, and its result has been processed by the Convex backend. So there's a potential race between isAuthenticated flipping to true and your query running, which is why sometimes you'd see log lines with isAuthenticated as true before your query runs, but to get the correct behavior every time, you'll want to skip your query when isAuthenticated is false, as erquhart suggested. isAuthenticated and isLoading should both be false only when the user is unauthenticated, so the redirect is the second useEffect looks fine to me. If you're still seeing your query run without a user with this fix, could you turn on verbose logging (new ConvexReactClient(url, { verbose: true })) and share the logs?
Olamide
OlamideOP4mo ago
Okay so this is what I have currently
function AuthLayout() {
const { isAuthenticated, isLoading } = useConvexAuth();
const navigate = Route.useNavigate();
const { data, isPending, error } = useQuery({
...convexQuery(api.users.currentUser, {}),
queryFn: isAuthenticated
? convexQuery(api.users.currentUser, {}).queryFn
: skipToken,
});

// useEffect(() => {
// if (!isPending && !error) {
// if (data === null) {
// navigate({ to: '/sign-in' });
// } else if (!data.isOnboardingComplete) {
// navigate({ to: '/onboarding' });
// }
// }
// }, [isPending, error, data, navigate]);

useEffect(() => {
if (!isLoading && !isAuthenticated) {
navigate({
to: '/sign-in',
});
}
}, [isAuthenticated, isLoading, navigate]);

useEffect(() => {
console.log(isAuthenticated, isLoading);
}, [isLoading, isAuthenticated]);

if (isLoading) {
return <p>Loading</p>;
}

return (
<div>
<Outlet />
</div>
);
}
function AuthLayout() {
const { isAuthenticated, isLoading } = useConvexAuth();
const navigate = Route.useNavigate();
const { data, isPending, error } = useQuery({
...convexQuery(api.users.currentUser, {}),
queryFn: isAuthenticated
? convexQuery(api.users.currentUser, {}).queryFn
: skipToken,
});

// useEffect(() => {
// if (!isPending && !error) {
// if (data === null) {
// navigate({ to: '/sign-in' });
// } else if (!data.isOnboardingComplete) {
// navigate({ to: '/onboarding' });
// }
// }
// }, [isPending, error, data, navigate]);

useEffect(() => {
if (!isLoading && !isAuthenticated) {
navigate({
to: '/sign-in',
});
}
}, [isAuthenticated, isLoading, navigate]);

useEffect(() => {
console.log(isAuthenticated, isLoading);
}, [isLoading, isAuthenticated]);

if (isLoading) {
return <p>Loading</p>;
}

return (
<div>
<Outlet />
</div>
);
}
After I just signed in I got this isLoading and isAuthenticated is false
No description
Olamide
OlamideOP4mo ago
After signing up isAuthenticated is false and isLoading is also false
sshader
sshader4mo ago
Can you share the code for your sign up page too? These are also both nested underneath the same ConvexAuthProvider, right?
Olamide
OlamideOP4mo ago
I would share that now The thing is something isAuthenticated is false and sometimes after sign up isAuthenticated is true there is inconsistency Yeah I have just one ConvexAuthProvider wrapping the entire app
const navigate = Route.useNavigate();
const { signIn } = useAuthActions();
const [error, setError] = useState<string | null>(null);
const [isSigningUp, setIsSigningUp] = useState(false);
const { control, handleSubmit } = useForm<SignUpFormData>({
resolver: zodResolver(signUpSchema),
defaultValues: {
name: '',
email: '',
password: '',
role: undefined,
},
});

const onSubmit = async (data: SignUpFormData) => {
setError(null);
setIsSigningUp(true);
console.log(data);
signIn('password', { ...data, isOnboardingComplete: false, flow: 'signUp' })
.then(() => {
console.log('Sign-up successful');
toast.success('Sign up successful. You can now start onboarding!');
navigate({
to: '/dashboard',
});
})
.catch((error) => {
setError('Sign-up failed. Please try again.');
console.error('Sign-up failed:', error);
})
.finally(() => {
setIsSigningUp(false);
});
};
const navigate = Route.useNavigate();
const { signIn } = useAuthActions();
const [error, setError] = useState<string | null>(null);
const [isSigningUp, setIsSigningUp] = useState(false);
const { control, handleSubmit } = useForm<SignUpFormData>({
resolver: zodResolver(signUpSchema),
defaultValues: {
name: '',
email: '',
password: '',
role: undefined,
},
});

const onSubmit = async (data: SignUpFormData) => {
setError(null);
setIsSigningUp(true);
console.log(data);
signIn('password', { ...data, isOnboardingComplete: false, flow: 'signUp' })
.then(() => {
console.log('Sign-up successful');
toast.success('Sign up successful. You can now start onboarding!');
navigate({
to: '/dashboard',
});
})
.catch((error) => {
setError('Sign-up failed. Please try again.');
console.error('Sign-up failed:', error);
})
.finally(() => {
setIsSigningUp(false);
});
};
This is the logic in the component the rest are just jsx Sign in also has the same issue but once I reload and change the url to dashboard it gets that they are authenticated It just doesn't make sense cause why do I need to hard reload for it to work?
Michal Srb
Michal Srb4mo ago
Indeed right now you have to wait for isAuthenticated manually: https://github.com/get-convex/convex-auth/issues/29
GitHub
Block signIn function on completed signIn · Issue #29 · get-con...
Right now there's a delay between signIn finishing and the client authenticating. This leads to a visible "bug" for email+password requiring verification: Sign up Verify Log out Sign ...