'supabase onAuthStateChange not re-rendering useEffect

I have the following custom hook useAuth, in which I am having a supabase.auth.onAuthStateChange event listener.

export const useAuth = () => {
  const [session, setSession] = useState(supabase.auth.session());

  const signOutUser = async () => {
    await supabase.auth.signOut();
    useAuthStore.setState({
      user: null,
    });
    setSession(null);
  };

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

  supabase.auth.onAuthStateChange(async (event, session) => {
    if (session) {      
      const { error: fetchError, data } = await fetchUser(session);
      if (fetchError) {
        await signOutUser();
      } else {
        if (data.length === 0) {         
          setSession(session);            
        } else {            
          setSession(session);            
        }
      }      
    } else {
      setSession(null);
    }
  }); 

  return session;
};

Now I am using react-router-dom@6 for routing.

This is my App.js file

function RequireAuth({ children }) {
  const session = useAuth();
  let location = useLocation();

  console.log({ children, session });

  if (!session) {
    return <Navigate to="/login" state={{ from: location }} replace />;
  }

  return children;
}

function App() {  
  return (    
    <BrowserRouter>
      <Routes>
        <Route
          path="/"
          element={
            <RequireAuth>
              <Home />
            </RequireAuth>
          }
        />
        <Route path="/login" element={<Login />} />        
        <Route
          path="/servers"
          element={
            <RequireAuth>
              <Servers />
            </RequireAuth>
          }
        />
        <Route path="*" element={<FourNotFour />} />
      </Routes>
    </BrowserRouter>
  );
}

Now my question is within useAuth i have a line like console.log({ session }); which prints out session object, but this line is getting printed only once, even after the auth state has changed to logged in, and setSession got executed. And thus on App.js file the method RequireAuth keep on executing only once thus after login my app is still on the login page, it's not re-routing to Home page

Why is that?



Solution 1:[1]

You should put your auth listener inside of a useEffect so you can react to changes. onAuthStateChange returns the listener, which you can unsubscribe from in the useEffect's cleanup function:

useEffect(() => {
  const { data: authListener } = supabase.auth.onAuthStateChange(
    async (event, session) => {
      if (session) {
        const { error: fetchError, data } = await fetchUser(session);
        if (fetchError) {
          await signOutUser();
        } else {
          if (data.length === 0) {
            setSession(session);
          } else {
            setSession(session);
          }
        }
      } else {
        setSession(null);
      }
    }
  );

  // cleanup function to unsubscribe
  return () => {
    if (authListener) authListener.unsubscribe();
  };
}, [supabase.auth]);

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Bennett Dams