Son
Son•11mo ago

isAuthenticated is always false (Expo)

Hey, - I have my auth.config.js set up correctly. - the back-end also reflects this. - I can sign in with clerk just fine. can anyone help?
export default {
providers: [
{
domain: "https://sqaure-grizzly-81.clerk.accounts.dev/",
applicationID: "convex",
},
]
};
export default {
providers: [
{
domain: "https://sqaure-grizzly-81.clerk.accounts.dev/",
applicationID: "convex",
},
]
};
No description
No description
31 Replies
Son
SonOP•11mo ago
Im using a monorepo setup with turbo repo and import the convex code into all my apps via a package. I can connect to the database across all my apps and run queries with no problems.
jamwt
jamwt•11mo ago
often we find the JWT step is the one that trips folks up. did you double check that? did you hit "apply" when you created the JWT? next question would be, is the publishable key set in your .env.local?
Son
SonOP•11mo ago
the convex url and clerk key successfully load into the environment via react-native-dotenv
export default function Root() {

const convex = new ConvexReactClient(CONVEX_URL,{
unsavedChangesWarning: false,
});

console.log('CONVEX_URL',CONVEX_URL); console.log('CLERK_PUBLISHABLE_KEY',CLERK_PUBLISHABLE_KEY)
return (
<ClerkProvider tokenCache={tokenCache} publishableKey={CLERK_PUBLISHABLE_KEY}>
<ConvexProviderWithClerk client={convex} useAuth={useAuth} >
<InitialLayout />
</ConvexProviderWithClerk>
</ClerkProvider>


);
}
export default function Root() {

const convex = new ConvexReactClient(CONVEX_URL,{
unsavedChangesWarning: false,
});

console.log('CONVEX_URL',CONVEX_URL); console.log('CLERK_PUBLISHABLE_KEY',CLERK_PUBLISHABLE_KEY)
return (
<ClerkProvider tokenCache={tokenCache} publishableKey={CLERK_PUBLISHABLE_KEY}>
<ConvexProviderWithClerk client={convex} useAuth={useAuth} >
<InitialLayout />
</ConvexProviderWithClerk>
</ClerkProvider>


);
}
No description
jamwt
jamwt•11mo ago
is this because isLoaded ? like, is this just b/c the clerk stuff is reloading? Does isLoaded ever become true?
Son
SonOP•11mo ago
when i reload the app this is the sequence if logs
Son
SonOP•11mo ago
No description
Son
SonOP•11mo ago
updated workflow, still no luck
import { useEffect } from "react";
import { View } from "react-native";
import "react-native-get-random-values";
import { CONVEX_URL,CLERK_PUBLISHABLE_KEY } from "@env";
import { ClerkProvider,useAuth } from "@clerk/clerk-expo";
import { ConvexProviderWithClerk,ConvexReactClient,useConvexAuth } from '@repo/api/core';

const tokenCache = {
async getToken(key: string) {
try {
return SecureStore.getItemAsync(key);
} catch (err) {
return null;
}
},
async saveToken(key: string,value: string) {
try {
return SecureStore.setItemAsync(key,value);
} catch (err) {
return;
}
},
};

const InitialLayout = () => {
const { isAuthenticated,isLoading } = useConvexAuth();
const { isSignedIn,isLoaded } = useAuth();


const router = useRouter();

useEffect(() => {
try {
console.log('clerk',{ isSignedIn,isLoaded })
console.log('convex',{ isAuthenticated,isLoading });
if (!isLoaded) return;
if (isSignedIn) {
router.replace("/home");
} else {
router.replace("/sign-up");
}
} catch (err) {
console.error(JSON.stringify(err,null,2));
console.error("error signing in",err);
}

},[isLoading,isAuthenticated,router,isSignedIn,isLoaded]);
return (
<>
<View className="flex-1 items-center justify-center bg-white">
<Slot />
</View>
</>
);
};

export default function Root() {

const convex = new ConvexReactClient(CONVEX_URL,{
unsavedChangesWarning: false,
});

console.log('CONVEX_URL',CONVEX_URL); console.log('CLERK_PUBLISHABLE_KEY',CLERK_PUBLISHABLE_KEY)

return (
<ClerkProvider tokenCache={tokenCache} publishableKey={CLERK_PUBLISHABLE_KEY}>
<ConvexProviderWithClerk client={convex} useAuth={useAuth}>
<InitialLayout />
</ConvexProviderWithClerk>
</ClerkProvider>
);
}
import { useEffect } from "react";
import { View } from "react-native";
import "react-native-get-random-values";
import { CONVEX_URL,CLERK_PUBLISHABLE_KEY } from "@env";
import { ClerkProvider,useAuth } from "@clerk/clerk-expo";
import { ConvexProviderWithClerk,ConvexReactClient,useConvexAuth } from '@repo/api/core';

const tokenCache = {
async getToken(key: string) {
try {
return SecureStore.getItemAsync(key);
} catch (err) {
return null;
}
},
async saveToken(key: string,value: string) {
try {
return SecureStore.setItemAsync(key,value);
} catch (err) {
return;
}
},
};

const InitialLayout = () => {
const { isAuthenticated,isLoading } = useConvexAuth();
const { isSignedIn,isLoaded } = useAuth();


const router = useRouter();

useEffect(() => {
try {
console.log('clerk',{ isSignedIn,isLoaded })
console.log('convex',{ isAuthenticated,isLoading });
if (!isLoaded) return;
if (isSignedIn) {
router.replace("/home");
} else {
router.replace("/sign-up");
}
} catch (err) {
console.error(JSON.stringify(err,null,2));
console.error("error signing in",err);
}

},[isLoading,isAuthenticated,router,isSignedIn,isLoaded]);
return (
<>
<View className="flex-1 items-center justify-center bg-white">
<Slot />
</View>
</>
);
};

export default function Root() {

const convex = new ConvexReactClient(CONVEX_URL,{
unsavedChangesWarning: false,
});

console.log('CONVEX_URL',CONVEX_URL); console.log('CLERK_PUBLISHABLE_KEY',CLERK_PUBLISHABLE_KEY)

return (
<ClerkProvider tokenCache={tokenCache} publishableKey={CLERK_PUBLISHABLE_KEY}>
<ConvexProviderWithClerk client={convex} useAuth={useAuth}>
<InitialLayout />
</ConvexProviderWithClerk>
</ClerkProvider>
);
}
i cant see where im going wrong here.
jamwt
jamwt•11mo ago
not sure. maybe @Michal Srb has an idea. I'm probably out of my depth on auth at this point 🙂 code and steps all look right to me
Son
SonOP•11mo ago
no issues with my imports from my package
No description
Son
SonOP•11mo ago
thanks for your help anyway
convex {"isAuthenticated": false, "isLoading": true}
convex {"isAuthenticated": false, "isLoading": true}
convex is always in a loading state
Michal Srb
Michal Srb•11mo ago
Hey @Son , what do your Convex logs look like? (You cand find them here: https://dashboard.convex.dev/deployment/logs )
Convex Dashboard
Manage your Convex apps
Son
SonOP•11mo ago
here you go
No description
No description
Michal Srb
Michal Srb•11mo ago
Got it, and this is what you expect from your app, ie you're hitting the right backend? Is there a way to see the "network tab" with React Native? Another helpful thing could be enabling verbose logging for the Convex client:
const convex = new ConvexReactClient(CONVEX_URL,{
unsavedChangesWarning: false,
verbose: true
});
const convex = new ConvexReactClient(CONVEX_URL,{
unsavedChangesWarning: false,
verbose: true
});
Then retry and see what it prints to the client console
Son
SonOP•11mo ago
No description
Son
SonOP•11mo ago
i dont see any indication that the auth flow is connected. im definitely connected the right backend
Son
SonOP•11mo ago
No description
Michal Srb
Michal Srb•11mo ago
You're using a cache, can you try without? We specifically skip the cache sometimes. Can you show the details of the 404? (Appreciate your patience debugging this, auth can be finicky to set up!)
Son
SonOP•11mo ago
got it working, you wont believe it... the 404 error mentioned that the JWT template name could not be found. i renamed the JWT template convex-randomnamehere in the clerk dashboard, and in my convex dashboard the name for the JWT was still "corvex" , i didn't realise they corresponded to each other for some odd reason. which caused the issue Thank you for the help, you got me to the bug!
Michal Srb
Michal Srb•11mo ago
Glad you figured it out!
Son
SonOP•11mo ago
are there any utility types to get the correct types for mutations, queries and actions. I'm using react query to manage complex async functions. i'm passing some of the mutations etc... as dependencies to my hooks, so i need the right types export type TAddEmailMutate = ({ email,type }: { email: string,type: string }) => Promise<any> for const addEmailMutate = useMutation(api.emailList.addEmailToList);
No description
Son
SonOP•11mo ago
No description
jamwt
jamwt•11mo ago
out of curiosity, why do you need react-query?
Michal Srb
Michal Srb•11mo ago
FunctionArgs<typeof api.foo.bla> and FunctionReturnType<typeof api.foo.bla> from convex/server
Son
SonOP•11mo ago
Before i make the database query i’m running a few async utility functions in the frontend, so if any of them fail, i can catch everything in a hook and update my ui. I believe React query is a great async function manager, and gives me iserror, ispending, issuccess out of the box for any async function i pass to it, not just fetches. It also gives me a reset function, so i can easily reset the ui state after the function has run, for example when i reset a text field ring-color to its default colour 500ms after it was set to green when the database query was successful. Thanks for getting back to me.
Michal Srb
Michal Srb•11mo ago
Before i make the database query i’m running a few async utility functions in the frontend, so if any of them fail, i can catch everything in a hook and update my ui.
I'm curious what is the end user experience? What do the utility functions do? (presumably they involve Convex mutations/actions)
Son
SonOP•11mo ago
The main reason why I reached for react query in this instance, was because i needed a hook at the top if my file which gives me access to the current state of the whole mutation, including the checks before the final convex database query. (i manually throw erros where i need to so RT catches it correctly) This is my hook
export function addEmailHook({email, type, addEmailMutate, addEmailToListChecks } : {
email: string;
type: string;
addEmailMutate: TAddEmailMutate
addEmailToListChecks: TAddEmailToListChecks;

}) {
const {
isError,
isPending,
isSuccess,
mutate: addEmail,
reset,
} = useRQMutation({
mutationKey: ["addEmail"],
mutationFn: async ({
email,
type,
}: TUser) => {
const { response } = await addEmailToListChecks({
email: email,
type: type,
});

if (!response.isSuccess) {
toast({
title: "Error",
description: response.message,
});
}

if (response.isSuccess) {
const { message } = await addEmailMutate({
email: email,
type: type,
});

toast({
title: "Success",
description: message,
});
}
},
onError: (error) => {
if (error instanceof ConvexError) {
console.log(error.data);
toast({
title: "Error",
description: error.data.message,
});
} else {
toast({
title: "Error",
description: error.message || 'Something went wrong, contact support. Error code: 100',
});
}
},
onSettled: () => {
setTimeout(() => {
reset();
},2000);
},
});


return { isError,isPending,isSuccess, addEmail };
}
export function addEmailHook({email, type, addEmailMutate, addEmailToListChecks } : {
email: string;
type: string;
addEmailMutate: TAddEmailMutate
addEmailToListChecks: TAddEmailToListChecks;

}) {
const {
isError,
isPending,
isSuccess,
mutate: addEmail,
reset,
} = useRQMutation({
mutationKey: ["addEmail"],
mutationFn: async ({
email,
type,
}: TUser) => {
const { response } = await addEmailToListChecks({
email: email,
type: type,
});

if (!response.isSuccess) {
toast({
title: "Error",
description: response.message,
});
}

if (response.isSuccess) {
const { message } = await addEmailMutate({
email: email,
type: type,
});

toast({
title: "Success",
description: message,
});
}
},
onError: (error) => {
if (error instanceof ConvexError) {
console.log(error.data);
toast({
title: "Error",
description: error.data.message,
});
} else {
toast({
title: "Error",
description: error.message || 'Something went wrong, contact support. Error code: 100',
});
}
},
onSettled: () => {
setTimeout(() => {
reset();
},2000);
},
});


return { isError,isPending,isSuccess, addEmail };
}
const addEmailMutate = useMutation(api.emailList.addEmailToList);

const { isError,isPending,isSuccess,addEmail } = addEmailHook({ addEmailMutate,addEmailToListChecks,email: user.email,type: user.type });
const addEmailMutate = useMutation(api.emailList.addEmailToList);

const { isError,isPending,isSuccess,addEmail } = addEmailHook({ addEmailMutate,addEmailToListChecks,email: user.email,type: user.type });
inside a form
<div
className={cn(
"absolute inset-0 -z-10 rounded-lg bg-white/2.5 ring-1 ring-white/15 transition peer-focus:ring-sky-300",
{
"ring-2 ring-red-500": isError,
"ring-2 ring-yellow-300": isPending,
"ring-2 ring-green-500": isSuccess,
},
)}
/>
</form>
<div
className={cn(
"absolute inset-0 -z-10 rounded-lg bg-white/2.5 ring-1 ring-white/15 transition peer-focus:ring-sky-300",
{
"ring-2 ring-red-500": isError,
"ring-2 ring-yellow-300": isPending,
"ring-2 ring-green-500": isSuccess,
},
)}
/>
</form>
the use case for RT is the out of the box function management. i'm very open to suggestions, if there is a better way with convex please let me know as im new to it. I just noticed, i didn't create a separate thread. sorry! truth is, for this particular use case i want the pending state, it just makes the UI cooler, even if its only for 300ms to 400ms
jamwt
jamwt•11mo ago
gotcha. useQuery returns undefined when it's pending
Son
SonOP•11mo ago
the docs say that, but it also says Once a query is loaded it will never be undefined again so it will never show the pending ui state again for that session right?. and that was the issue i was facing. there was also no easy way for me to reset the state of the ui.
jamwt
jamwt•11mo ago
got it. so the desire is: a mutation is invoked in some other part of the app, and you know it will invalidate a query that's currently subscribed, and so you want to be able to have that query represented as a pending state until the new value is streamed down from the server. does that sound right?
jamwt
jamwt•11mo ago
if so, you can use convex's optimistic updates for this: https://docs.convex.dev/client/react/optimistic-updates
Optimistic Updates | Convex Developer Hub
Even though Convex queries are completely reactive, sometimes you'll want to
jamwt
jamwt•11mo ago
gotcha. yeah, in convex the way you'd mark an update pending or conditional or whatever would typically be using optimistic update apis linked above

Did you find this page helpful?