Search code examples
reactjsreact-router-dom

Protect a SPA application in React with a login - why does this methodology not work?


I am learning ReactJS so I am completely new at this.

I created an authentication/authorization service that I know works in React just fine. What I am trying to do is protect the primary application with a login. As I understand, I want a high-order component such as this in order to protect the core of the application:

const withAuth = (Component) => {
  const AuthenticatedComponent = () => {
    const isAuthenticated = MyService.isUserAuthenticated();
    if (!isAuthenticated) {
      return <Navigate to="/login" />;
    }
    return <Component />;
  };

  return AuthenticatedComponent;
};

export default withAuth;

I have two components that are pretty simple to test the theory that are both very similar. Essentially they look like this:

export default function DefaultLandingPage() {
  return(
    <>
    <div>
      This would be the dashboard page after login.
    </div>
    </>
  );
}
export default function LoginPage() {
  return(
    <>
    <div>
      This is where the user would log in.
    </div>
    </>
  );
}

I tried to use these in my App.js file.

First I tried this:

function App() {
  return (
    <>
    <Routes>
      <Route path="/" exact component={withAuth(DefaultLandingPage) } />
      <Route path="/login" element={ <LoginPage /> } />
    </Routes>
    </>
  );
}

That results in:

Matched leaf route at location "/" does not have an element or Component. This means it will render an with a null value by default resulting in an "empty" page.

I tried changing it to:

<Route path="/" exact element={ withAuth(DefaultLandingPage) } />

Which results in:

Warning: Functions are not valid as a React child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than return it.

Can someone point me in the proper direction?

I did find this post and am headed down that route right now; however, I am not sure what my incorrect logic is here.

Can someone explain why my logic doesn't work here?

What concerns me, is that I want to also protect the pages with fine-grained privileges and was hoping to create something that would function like this:

<Route path="/" exact element={ withAuth(DefaultLandingPage) } />
<Route path="/some-sub-area" element={ withAuth(withPermission(SomeSubArea, 'permission-xyz')) } />

Solution

  • For your first case, make sure your prop component is capitalized to Component per the router documentation. For your second issue, your withAuth function returns a function reference to AuthenticatedComponent rather than treating it as a functional component. Opting to return <AuthenticatedComponent/> should send you in the right direction!