skip to Main Content

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')) } />

2

Answers


  1. 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!

    Login or Signup to reply.
  2. The react-router-dom Route component hasn’t any component, so this is the cause of the first error regarding a missing element.

    The Route component’s element prop takes only a React.ReactNode, e.g. it takes a JSX literal. withAuth(DefaultLandingPage) isn’t valid JSX.

    You’ve a couple options:

    1. Export a decorated version of the DefaultLandingPage component, to be rendered as JSX.

      const DefaultLandingPage = () => {
        ...
      };
      
      export default withAuth(DefaultLandingPage);
      
      function App() {
        return (
          <Routes>
            <Route path="/" element={<DefaultLandingPage /> } />
            <Route path="/login" element={<LoginPage />} />
          </Routes>
        );
      }
      
    2. Implement protected routes in a more conventional method, as per the Stackoverflow answer you linked. Using Higher Order Components for authentication as a little old these days.

      const AuthenticatedComponent = () => {
        const isAuthenticated = MyService.isUserAuthenticated();
      
        return isAuthenticated
          ? <Outlet />
          : <Navigate to="/login" replace />;
      };
      
      function App() {
        return (
          <Routes>
            <Route element={<AuthenticatedComponent />}>
              <Route path="/" element={<DefaultLandingPage /> } />
            </Route>
            <Route path="/login" element={<LoginPage />} />
          </Routes>
        );
      }
      

    If the eventual goal is to also use role-based route protection then you’d implement something similar to the following:

    const PermissionRoute = ({ permissions = [] }) => {
      const permission = /* wherever you get current user's permission */
    
      return !permissions.length || permissions.includes(permission) ? (
        <Outlet />
      ) : (
        <Navigate to="/login" replace /> // <-- or any safe non-permission route
      );
    };
    
    function App() {
      return (
        <Routes>
          ...
          <Route element={<PermissionRoute permissions={["...", ...]} />}>
            ...
          </Route>
          ...
        </Routes>
      );
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search