reactjsreact-nativeexposupabase

Invalid refresh token crash Expo Supabase


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:

  1. login to my simulator on my pc, and close app
  2. login to my phone app
  3. logout of my phone (so the session is destroyed)
  4. wait for like 2 hours
  5. 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:

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);


Solution

  • 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:

      const [loaded] = useFonts({
       SpaceMono: require('../../assets/fonts/SpaceMono-Regular.ttf'),
      });
    
      useEffect(() => {
       if (loaded) {
       SplashScreen.hideAsync();
       }
      }, [loaded]);
    
      if (!loaded) {
       return null;
      }
    

    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.