Search code examples
javascriptreactjsreact-nativesupabase

Supabase onAuthStateChanged - How do I properly wait for the request to finish prevent flickering with useEffect?


Everything auth-wise is working fine. I even have a loading state setup so that the loader shows until the state is changed, but I still get this flickering on reload. This flickering only happens with Supabase. I was using the Firebase version before and it worked perfectly with my code.

Here is a video for reference: https://i.sstatic.net/rxGDn.jpg

Edit: Updated code to current version

export default function Navigation() {
  const { user, setUser } = useContext(AuthenticatedUserContext);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    const session = supabase.auth.session();
    setUser(session?.user ?? null);

    const { data: listener } = supabase.auth.onAuthStateChange((_: any, session: any) => {
      setUser(session?.user ?? null);
    });

    setIsLoading(false);
    return () => {
      listener?.unsubscribe();
    };
  }, []);

  if (isLoading) {
    return (
      <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
        <ActivityIndicator color={Theme.colors.purple} size="large" />
      </View>
    );
  }

  return (
    <NavigationContainer linking={LinkingConfiguration}>{user ? <AppStack /> : <AuthStack />}</NavigationContainer>
  );
}

Solution

  • Ok, Supabase has released some updates since I first asked this question. Here is how I am now able to stop flickering when loading the application.

    First, we need to set up our AuthContext for our application. Be sure to wrap your App.tsx with the <AuthContextProvider>.

    AuthContext.tsx

    import React, { createContext, useContext, useEffect, useState } from 'react';
    
    import { Session, User } from '@supabase/supabase-js';
    
    import { supabase } from '../config/supabase';
    
    export const AuthContext = createContext<{ user: User | null; session: Session | null }>({
      user: null,
      session: null,
    });
    
    export const AuthContextProvider = (props: any) => {
      const [userSession, setUserSession] = useState<Session | null>(null);
      const [user, setUser] = useState<User | null>(null);
    
      useEffect(() => {
        supabase.auth.getSession().then(({ data: { session } }) => {
          setUserSession(session);
          setUser(session?.user ?? null);
        });
    
        const { data: authListener } = supabase.auth.onAuthStateChange(async (event, session) => {
          console.log(`Supabase auth event: ${event}`);
          setUserSession(session);
          setUser(session?.user ?? null);
        });
    
        return () => {
          authListener.subscription;
        };
      }, []);
    
      const value = {
        userSession,
        user,
      };
      return <AuthContext.Provider value={value} {...props} />;
    };
    
    export const useUser = () => {
      const context = useContext(AuthContext);
      if (context === undefined) {
        throw new Error('useUser must be used within a AuthContextProvider.');
      }
      return context;
    };
    

    Now, if you're using React Navigation like me we need to check if we have a valid user to send them to the logged-in home screen. Here's how I do it.

    Navigation.tsx

    export default function Navigation() {
      const { user } = useUser();
    
      return (
        <NavigationContainer linking={LinkingConfiguration}>
          {user ? <AppStackNavigator /> : <AuthStackNavigator />}
        </NavigationContainer>
      );
    }