Search code examples
reactjsauthenticationrefactoringauth0

How to refactor common react code for a sign in redirect?


I'm working on a React application and a lot (but not all) of the pages include this code:

export default function MyPage() {
  const { isAuthenticated, loginWithRedirect } = useAuth0<User>();

  const handleSignIn = async () => {
    await auth.login(loginWithRedirect);
  };

  if (!isAuthenticated) {
    return <SignIn onSignIn={handleSignIn} />;
  }

  // ...
}

Looking for some advice on how to refactor this so it isn't duplicated across multiple pages. Would a hook be appropriate? Or some kind of parent component wrapper???


Solution

  • Have you looked at the built in HOC withAuthenticationRequired?

    If you need more customization you could effectively recreate withAuthenticationRequired but can customize it a bit more:

    Component wrapper works great:

    const RequiresAuthenticatedUser = ({children}) => {
        const { isAuthenticated, isLoading, error, loginWithRedirect } = useAuth0<User>();
    
        const shouldRequireLogin = !isLoading && !isAuthenticated && !error
    
        useEffect(() => {
           if(!shouldRequireLogin) return;
           auth.login(loginWithRedirect)
        },[shouldRequireLogin,loginWithRedirect])
    
        if(error) {
          // something went wrong, return an error message
          return <>Error</>
        }
    
        if(isLoading) {
          return null; // or a spinner, I think null is appropriate
        }
    
        if(!isAuthenticated) {
           auth.login(loginWithRedirect)
           return nulll;
        }
    
        return <>{children}</>
    }
    

    Can be used like this:

    function MyPage() {
      ...
    }
    
    export default () => <RequiresAuthenticatedUser><MyPage/></RequiresAuthenticatedUser/>
    

    You can use the above to create a nifty little HOC to wrap your components in:

    const requiresAuthenticatedUser = (Cmp) => (props) => <RequiresAuthenticatedUser><Cmp {...props}/></RequiredAuthenticatedUser>
    
    function MyPage() {
     ...
    }
    
    export default requiresAuthenticatedUser(MyPage)
    

    One reason I strongly advocate for a wrapping component (or HOC) instead of a hook is because it moves the logical concerns of "do I even have a user" outside of the components that require a user. Otherwise, it's extremely common for "I'm not sure if I have a user yet" logic to pollute components that really should only be concerned with rendering the user they know they have.