Search code examples
javascriptreactjsreact-router-dom

Child component is not being rendered with Outlet


I am having problems displaying a child component.

App.js

<Route
  element={<ProtectedRoutes allowedRoles={[userRoles.FIELD_FORCE_MANAGER]} />}
>
  <Route
    path="/dashboard-ff"
    element={
      <DashboardWrapper>
        <FieldForceDashboard />
      </DashboardWrapper>
    }
  />
</Route>

Right now I have the allowed userRole. So my <ProtectedRoutes> has no issue. But anyway that is the code:

import React from "react";
import { Navigate, Outlet } from "react-router-dom";
import { useSelector } from "react-redux";
import PropTypes from "prop-types";

const ProtectedRoutes = ({ allowedRoles }) => {
  const { isAuthenticated, userRole } = useSelector((state) => state.auth);

  return isAuthenticated && allowedRoles.includes(userRole)
    ? <Outlet />
    : <Navigate to="/" />;
};

ProtectedRoutes.propTypes = {
  allowedRoles: PropTypes.arrayOf(PropTypes.string).isRequired,
};

export default ProtectedRoutes;

DashboardWrapper:

return (
  <>
    <Sidenav
      color={sidenavColor}
      brand={(transparentSidenav && !darkMode) || whiteSidenav
        ? brandDark
        : brandWhite
      }
      brandName="Pharm"
      routes={routes}
      onMouseEnter={handleOnMouseEnter}
      onMouseLeave={handleOnMouseLeave}
    />
    <Outlet />
  </>
);

Note my DashboardWrapper is being rendered. The problem is with rendering FieldForceDashboard. Note that FieldForceDashboard is being rendered if I remove DashboardWrapper in App.js as follows:

<Route
  element={<ProtectedRoutes allowedRoles={[userRoles.FIELD_FORCE_MANAGER]} />}
>
  <Route
    path="/dashboard-ff"
    element={<FieldForceDashboard />}
  />
</Route>

Solution

  • Issue

    If FieldForceDashboard is rendered when not directly wrapped by the DashboardWrapper then the issue is just that DashboardWrapper isn't rendering its children prop.

    DashboardWrapper is a wrapper component so it should render the children prop. Outlet components are used by layout route components to allow nested routes to render their element content.

    Solution Suggestions

    Wrapper Component

    Update DashboardWrapper to render children instead of Outlet.

    const DashboardWrapper = ({ children }) => {
      ...
    
      return (
        <>
          <Sidenav
            color={sidenavColor}
            brand={(transparentSidenav && !darkMode) || whiteSidenav ? brandDark : brandWhite}
            brandName="Pharm"
            routes={routes}
            onMouseEnter={handleOnMouseEnter}
            onMouseLeave={handleOnMouseLeave}
          />
          {children}
        </>
      );
    };
    
    <Route
      element={(
        <ProtectedRoutes
          allowedRoles={[userRoles.FIELD_FORCE_MANAGER]}
        />
      )}
    >
      <Route
        path="/dashboard-ff"
        element={(
          <DashboardWrapper>        // <-- renders children
            <FieldForceDashboard /> // <-- child
          </DashboardWrapper>
        )}
      />
     </Route>
    

    Layout Route Component

    If you wish for DashboardWrapper to be a layout route then I suggest renaming it to DashboardLayout and render it as a layout route.

    Example:

    const DashboardLayout = () => {
      ...
    
      return (
        <>
          <Sidenav
            color={sidenavColor}
            brand={(transparentSidenav && !darkMode) || whiteSidenav ? brandDark : brandWhite}
            brandName="Pharm"
            routes={routes}
            onMouseEnter={handleOnMouseEnter}
            onMouseLeave={handleOnMouseLeave}
          />
          <Outlet />
        </>
      );
    };
    
    <Route
      element={(
        <ProtectedRoutes
          allowedRoles={[userRoles.FIELD_FORCE_MANAGER]}
        />
      )}
    >
      <Route element={<DashboardLayout />}>
        <Route path="/dashboard-ff" element={<FieldForceDashboard />} />
      </Route>
    </Route>