I am currently developing an app for work with react native, expo and supabase.
The problem i am facing is the following:
ERROR [AuthApiError: Invalid Refresh Token: Refresh Token Not Found]
Which happens when my session is destroyed, the error itself isnt the problem for me the problem is that my app displays a white screen when getting this error. And i havent found out why it does that.
This is a piece of code related to the error:
import { supabase } from "@/lib/supabase";
import { AuthApiError, Session } from "@supabase/supabase-js";
import { router } from "expo-router";
import { PropsWithChildren, createContext, useContext, useEffect, useState } from "react";
type AuthData = {
session: Session | null;
profile: any;
loading: boolean;
isAdmin: boolean;
};
const AuthContext = createContext<AuthData>({
session: null,
profile: null,
loading: true,
isAdmin: false,
});
export default function AuthProvider({ children }: PropsWithChildren) {
const [newSession, setNewSession] = useState<Session | null>(null);
const [profile, setProfile] = useState<any>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
supabase.auth.getSession().then(({ data: { session } }) => {
setNewSession(session);
if (session) {
// Fetch profile when a session exists
supabase
.from('profiles')
.select('*')
.eq('id', session.user.id)
.single()
.then(({ data }) => {
setProfile(data || null);
setLoading(false);
})
} else {
console.log('No session found');
setLoading(false);
router.push('/(auth)/sign-in');
}
});
supabase.auth.onAuthStateChange(async (_event, session) => {
setNewSession(session);
if (session) {
// fetch profile
const { data } = await supabase
.from('profiles')
.select('*')
.eq('id', session.user.id)
.single();
setProfile(data || null);
}
});
}, []);
return <AuthContext.Provider value={{session: newSession, loading, profile, isAdmin: profile?.role === 'ADMIN'}}>{children}</AuthContext.Provider>;
}
export const useAuth = () => useContext(AuthContext);
The steps i took to replicate my error:
- login to my simulator on my pc, and close app
- login to my phone app
- logout of my phone (so the session is destroyed)
- wait for like 2 hours
- open app simulator again and see the error.
If i do this the other way arround i get a white screen on my phone.
I have tried to use a try catch, and to check if there is no session. And if so send the user back to the sign in page. But that didnt prevent my app from crashing.
I am tempted to think that the crash occurs when an error occurs.
I realy hope anyone has some tips for me.
EDIT:
- I added my whole code instead of just the useEffect
EDIT 2:
@TommyBs gave me a tip that may would have worked, but i might have done it the wrong way?
Here is the code i made after @TommyBs his tip:
useEffect(() => {
// Set up the listener for auth state changes
const { data: { subscription } } = supabase.auth.onAuthStateChange(
(event, session) => {
if (event === 'SIGNED_OUT') {
// Handle sign out by setting session to null
setNewSession(null);
router.push('/(auth)/sign-in');
setLoading(false);
} else if (session) {
// Update the session state if a session is present
setNewSession(session);
setLoading(false);
} else {
// This handles the case when there is no session (e.g., the user is not logged in)
setNewSession(null);
router.push('/(auth)/sign-in');
setLoading(false);
}
}
);
// Clean up the subscription on component unmount
return () => {
subscription.unsubscribe();
};
}, [router]);
useEffect(() => {
if (newSession) {
console.log('session found');
const fetchProfile = async () => {
const { data } = await supabase
.from('profiles')
.select('*')
.eq('id', newSession.user.id)
.single();
setProfile(data || null);
}
fetchProfile(); // Call the async function
} else {
console.log(' No session found');
}
}, [newSession]);
Edit 3:
export default function AuthProvider({ children }: PropsWithChildren) {
const [newSession, setNewSession] = useState<Session | null>(null);
const [profile, setProfile] = useState<any>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Set up the listener for auth state changes
const { data: { subscription } } = supabase.auth.onAuthStateChange(
(event, session) => {
if (event === 'SIGNED_OUT') {
// Handle sign out by setting session to null
setNewSession(null);
//router.push('/(auth)/sign-in');
setLoading(false);
} else if (session) {
// Update the session state if a session is present
setNewSession(session);
setLoading(false);
console.log(session);
// fetch profile
console.log('session found');
setTimeout(async () => {
const { data } = await supabase
.from('profiles')
.select('*')
.eq('id', session.user.id)
.single();
setProfile(data || null);
}, 0);
} else {
// This handles the case when there is no session (e.g., the user is not logged in)
setNewSession(null);
//router.push('/(auth)/sign-in');
setLoading(false);
}
}
);
// Clean up the subscription on component unmount
return () => {
subscription.unsubscribe();
};
}, []);
return <AuthContext.Provider value={{session: newSession, loading, profile, isAdmin: profile?.role === ‘ADMIN’}}>{children}</AuthContext.Provider>; }
export const useAuth = () => useContext(AuthContext);
2
Answers
The whole issue luckily wasnt because of the error invalid refresh token. It was part of the issue, but it wasnt the direct cause of the issue.
The issue had to do with the following piece of code:
If the font didnt load the page never loads and it just shows a white screen. What fixed my problem was to look up the official way to do this. So i found the following in the docs of expo. https://docs.expo.dev/versions/latest/sdk/splash-screen/
I followed this and used the recommended way of loading the fonts, and hiding the splashscreen. And it Fixed the whole problem for me.
For any questions or needed information just comment and ill explain happily.
I think you’ve had exactly the same issue I had. You don’t need the call to
getSession
from supabase in youruseEffect
. Just add anelse
statement in your auth state change handler to set your state tonull
as well and rely on that.I was tearing my hair out over this and unfortunately my question just got downvoted on SO. However on the supabase discord I was pointed out to the fact that calling other supabase functions in the auth state change handler can have side effects – https://supabase.com/docs/reference/javascript/auth-onauthstatechange.
You should wrap the profile fetch in a
setTimeout
within your authState change when you have a session. Or have anotheruseEffect
that usesnewSession
as a dependency e.g