Nested Protected routes and react-router race condition - reactjs

I am on react-router v6. I have an app that I've created Protected routes for and so far it works. However I have requirements to protect a few routes that are nested (within a protected route) which is where bugs are occurring.
My attempt to explain:
I have a hook that will fetch necessary member information at the top most level. This is used in tandem with useReactiveVar w/ apollo and a graphql backend. This works for the most part.
While this fetches, I have one level of protected routing -- this will check if the user is logged in via `localStorage.getItem('access_token').
In my second level of protected routing, it'll try to read data from the aforementioned member information. This will be undefined while the information is being fetched.
The first level of protected routing is resolved. My second level of protection is then ignored since member data is null. I think this is a race condition?
My code:
// ProtectedRoute.tsx
export const ProtectedRoute = ({
redirectPath = Routes.Landing,
isAllowed,
noBaseLayout,
}: ProtectedRouteProps) => {
if (!isAllowed) {
return <Navigate to={redirectPath} replace />;
}
if (noBaseLayout) {
return <Outlet />;
}
return (
<BaseLayout>
<Outlet />
</BaseLayout>
);
};
export default function routes() {
const isLoggedIn = localStorage.getItem('access_token');
const currentUser = useReactiveVar(currentMeVar); // This will be null at first until it resolves
const elements: RouteObject[] = [
// Unauth'd routes. Visiting these routes *with* a user will direct back to the dashboard
{
element: (
<ProtectedRoute isAllowed={!isLoggedIn} redirectPath={Routes.Dashboard} noBaseLayout />
),
children: [
{
path: Routes.Landing,
element: <LandingPage />,
},
{
path: Routes.PatientLoginToken,
element: <LoginTokenPage scope={UserScope.Member} />,
},
{
path: Routes.CaregiverLoginToken,
element: <LoginTokenPage scope={UserScope.Caregiver} />,
},
{
path: Routes.PatientLogin,
element: <PatientLoginPage />,
},
{
path: Routes.RequestLoginEmail,
element: <RequestEmailLoginPage />,
},
{ path: Routes.CaregiverLogin, element: <CaregiverLoginPage /> },
{ path: Routes.CaregiverCompleteSignup, element: <CaregiverCompleteSignupPage /> },
{
path: Routes.PatientSignup,
element: <PatientSignupPageOneStep />,
},
{
path: Routes.PatientSignupClient,
element: <PatientSignupPageOneStep />,
},
],
},
// Auth'd routes. Visiting these routes *without* a user will redirect back to the landing page
{
element: <ProtectedRoute isAllowed={!!isLoggedIn} />,
children: [
{ path: Routes.Dashboard, element: <DashboardPage /> },
{ path: Routes.ContentLibrary, element: <ContentLibraryBrowsePage /> },
// Auth'd, but user has no covered life yet. This will redirect back to the dashboard page
{
element: (
<ProtectedRoute
isAllowed={currentUser?.member.hasCoveredLifeAttached}
redirectPath={Routes.Dashboard}
noBaseLayout
/>
),
children: [
{ path: Routes.Scheduling, element: <ScheduleSessionTakeoverPage /> },
{ path: Routes.Session, element: <EmbeddedSessionPage /> },
{ path: Routes.PostSession, element: <PostSessionPage /> },
{ path: Routes.Sessions, element: <SessionsPage /> },
{ path: Routes.Profile, element: <ProfilePage /> },
{ path: Routes.ContentLibraryDetail, element: <ContentLibraryDetailPage /> },
{
path: Routes.Stats,
element: <StatsPage />,
children: [
{
path: Routes.BloodPressureStats,
element: <StatsPage.BloodPressureChart />,
index: true,
},
{ path: Routes.WeightStats, element: <StatsPage.WeightChart /> },
{ path: Routes.MoodStats, element: <StatsPage.MoodChart /> },
{
path: Routes.PhysicalActivityStats,
element: <StatsPage.PhysicalActivityChart />,
},
{
path: Routes.HeartRateStats,
element: <StatsPage.HeartRateSessionsView />,
},
{ path: Routes.HeartRateStatsDetail, element: <StatsPage.HeartRateSignalsView /> },
{
path: Routes.BloodOxygenStats,
element: <StatsPage.BloodOxygenSessionsView />,
},
{
path: Routes.BloodOxygenStatsDetail,
element: <StatsPage.BloodOxygenSignalsView />,
},
{
path: Routes.StepsStats,
element: <StatsPage.StepsChart />,
},
{
path: Routes.SleepStats,
element: <StatsPage.SleepChart />,
},
],
},
],
},
],
},
{ path: '*', element: <NotFoundPage /> },
];
return useRoutes(elements);
}

Related

(React-Router) Is there a better way to do page routing for different types of users than manually switching the router being used?

I'm a bit new to two-sided applications (and to React). I'm currently building an application that has both a "user" and a "business" side, and the type of user is decided after the user authenticates themselves. For that, my current approach is to simply create multiple versions of routers, and depending on the redux state for the user's type, I am dynamically loading in a different router. In the case that a user isn't authenticated, I am making use of a "generic" default router. Once the user logs in, the router is then switched to the appropriate one under App.js
I am wondering if there is a better way to do this, particularly because after a user authenticates themselves, there is a significant delay (a couple of seconds) in the router being switched in the background. For example, the route "/" goes to the landing page react component for the "generic" router, but goes to the dashboard page for the "user" router. Once the user logs in, I redirect them to the "/" router to move them to the dashboard page, however there is a weird delay and the user is actually taken back to the landing page for a few seconds, then the page re-builds to the proper user dashboard that is expected at the "/" route for an authenticated user.
Here is the code for the different types of routers:
export function UserRouter() {
return useRoutes([
{
path: '/learn',
element: <DashboardLayout />,
children: [
{ path: ':id', element: <Learn />},
]
},
{
path: '/dashboard',
element: <DashboardLayout />,
children: [
{ path: 'overview', element: <Overview /> },
{ path: 'tasks', element: <Tasks /> },
{ path: 'community', element: <Community /> },
{ path: 'announcements', element: <Announcements /> },
],
},
{ path: '/companies',
element: <DashboardLayout />,
children: [
{ path: ':id', element: <Company />},
]
},
{
path: '/termDocuments',
element: <EmptyPage />,
children: [
{ path: ':embedURL', element: <AirtableEmbedPage />},
]
},
{
path: '/',
element: <LogoOnlyLayout />,
children: [
{ path: '/', element: <Navigate to="/dashboard/overview" /> },
{ path: 'login', element: <Login /> },
{ path: 'register', element: <UserRegister /> },
{ path: 'landing', element: <Landing />},
{ path: '404', element: <NotFound /> },
{ path: '*', element: <Navigate to="/404" /> },
],
},
{ path: '*', element: <Navigate to="/404" replace /> },
]);
}
export function BusinessRouter() {
return useRoutes([
{
path: '/dashboard',
element: <DashboardLayout />,
children: [
{ path: 'overview', element: <BusinessOverview /> },
{ path: 'home', element: <BusinessHomePage /> },
{ path: 'community', element: <BusinessCommunity /> },
{ path: 'announcements', element: <BusinessAnnouncements /> },
],
},
{ path: '/companies',
element: <DashboardLayout />,
children: [
{ path: ':id', element: <Company />},
]
},
{
path: '/termDocuments',
element: <EmptyPage />,
children: [
{ path: ':embedURL', element: <AirtableEmbedPage />},
]
},
{
path: '/',
element: <LogoOnlyLayout />,
children: [
{ path: '/', element: <Navigate to="/dashboard/overview" /> },
{ path: 'login', element: <BusinessLogin /> },
{ path: 'register', element: <UserRegister /> },
{ path: 'landing', element: <Landing />},
{ path: '404', element: <NotFound /> },
{ path: '*', element: <Navigate to="/404" /> },
],
},
{
path: '/business',
element: <DashboardLayout />,
children: [
{ path: 'app', element: <Overview />}
],
},
{ path: '*', element: <Navigate to="/404" replace /> },
]);
}
export function GenericRouter() {
return useRoutes([
{
path: '/',
element: <LogoOnlyLayout/>,
children: [
{path: '/', element: <Landing/>},
{path: '404', element: <NotFound/>},
{path: 'founder', element: <Founders/>},
{path: '*', element: <Navigate to="/"/>},
// {path: '*', element: <Navigate to="/404"/>},
],
},
{
path: '/business',
element: <LogoOnlyLayout/>,
children: [
{path: 'login', element: <BusinessLogin/>},
{path: 'register', element: <UserRegister/>},
],
},
{
path: '/user',
element: <LogoOnlyLayout/>,
children: [
{path: 'login', element: <Login/>},
{path: 'register', element: <UserRegister/>},
],
},
{path: '*', element: <Navigate to="/" replace/>},
// {path: '*', element: <Navigate to="/404" replace/>},
]);
}
And here is the actual switching of the router based off of a redux variable that is updated in the background (within App.js)
const selectRouter = (userType) => {
switch (userType) {
case USER:
return <UserRouter/>
case BUSINESS:
return <BusinessRouter/>
default:
return <GenericRouter/>
}
}
function RouterChoice() {
const userType = useSelector(state => state.auth?.userType)
return selectRouter(userType)
}
export default function App() {
return (
<StylesProvider injectFirst>
<ThemeProvider>
<ScrollToTop />
<BaseOptionChartStyle />
<RouterChoice />
</ThemeProvider>
</StylesProvider>
);
}

ReactJs not displaying route children - useRoutes

I am having a problem where children element of the route not displaying in the correct URL as example like if my URL is http://localhost:3000/dash by default it will go to http://localhost:3000/dash/default. I even already put the component in the children element. I am trying to wrap the children element with DashboardLayout and render its children inside DashboardLayout. I dont get a clue on this problem.
Below are code from the file
routes.js
export const routes = [
{
path: "dash",
element: <DashboardLayout />,
children: [
{
path: "*",
elment: <Navigate to={"/dash/default"} />,
},
{
path: "",
element: <Navigate to={"/dash/default"} />,
},
{
path: "default",
element: <Home />,
},
],
},
{
path: "*",
children: [
{
path: "*",
element: <Home />
},
],
},
];
Dashboardlayout.js
const DashboardLayout = ({ children }) => {
<React.Fragment>
<div>{children}</div>
</React.Fragment>;
};
export default DashboardLayout;

How to lazy load route with react location?

I am quite new to React, and I have a project that uses react-location
I would like to load some routes lazily only if the path is activated.
My current config is:
my base routing module
export const routes: Route[] = [
...HomeModuleRoutes, // I want this to be instantly loaded.
...LazyRoutes, // I want this to be lazy loaded.
{ path: '/', element: <Navigate to="/home" /> },
];
those 2 constants look like this :
home.routing.ts
export const routes: Route[] = [
{
path: 'home',
element: <HomeTemplate />,
children: [
{
path: '/',
element: <HomePage />,
},
],
},
];
lazy.routing.ts
export const LazyRoutes: Route[] = [
{
path: 'test',
element: <LazyTemplate />,
children: [
{
path: '/',
element: <LazyList />,
},
{
path: 'add',
element: <LazyAdd />,
},
{
path: 'detail',
element: <LazyDetail />,
},
],
},
];
I don't quite see documentation or an example on this, is it just wrapping the component with <Suspense>?
You should import your component with React.lazy like:
const HomeTemplate = React.lazy(() => import("./HomeTemplate "));
check this example for react-router v6 lazy loading

Delete `,` prettier/prettier and Parse errors in imported module './routes'

I download a MUI template for a React web-base app.
In Routes.js file, I commented out a line
and deleted the "," before this line. Later I uncomment the line and add the ",", so it looks the same as before; however, there is an issue as follows: when npm start, it fails to compile.
I'm beginner for reactjs and can't understand what this means.
src\App.js
Line 2:20: Parse errors in imported module './routes': Unexpected token, expected "," (26:8) (26:8) import/no-named-as-default
Line 2:20: Parse errors in imported module './routes': Unexpected token, expected "," (26:8) (26:8) import/no-named-as-default-member
src\routes.js
Line 24:52: Delete `,` prettier/prettier
Line 36:55: Delete `,` prettier/prettier
Search for the keywords to learn more about each error.
routes.js is as follows:
import { Navigate, useRoutes } from 'react-router-dom';
// layouts
import DashboardLayout from './layouts/dashboard';
import LogoOnlyLayout from './layouts/LogoOnlyLayout';
//
import Login from './pages/Login';
import Register from './pages/Register';
import DashboardApp from './pages/DashboardApp';
import Products from './pages/Products';
import Blog from './pages/Blog';
import User from './pages/User';
import NotFound from './pages/Page404';
// ----------------------------------------------------------------------
export default function Router() {
return useRoutes([
{
path: '/dashboard',
element: <DashboardLayout />,
children: [
{ element: <Navigate to="/dashboard/app" replace /> },
{ path: 'app', element: <DashboardApp /> },
{ path: 'user', element: <User /> },
{ path: 'products', element: <Products /> },
// { path: 'blog', element: <Blog /> },
]
},
{
path: '/',
element: <LogoOnlyLayout />,
children: [
{ path: 'login', element: <Login /> },
{ path: 'register', element: <Register /> },
{ path: '404', element: <NotFound /> },
{ path: '/', element: <Navigate to="/dashboard" /> },
{ path: '*', element: <Navigate to="/404" /> },
]
},
{ path: '*', element: <Navigate to="/404" replace /> }
]);
}
I fixed this question by adding two lines in .eslintrc file
"import/no-named-as-default": 0,
"import/no-named-as-default-member": 0,
that means, I unactive that two rules so no compliling error will be caused by these two rules

How to pass Props into React Router 6 so it can be accessed in mapStateToProps(state,ownProps)?

I am using redux for one of my component. In mapStateToProps I like to use the id of the URL Params, but in no ways I am able to get the id of the url in that function.
function mapStateToProps(state, ownProps) {
**const recId = ownProps.id;**
const reconciliationRule =
recId && state.reconciliationRules.length > 0
? getReconciliationById(state.reconciliationRules, recId)
: newReconciliationRule;
return {
reconciliationRule,
reconciliationRules: state.reconciliationRules,
};
My route.js looks like below
const routes = [
{
path: "app",
element: <DashboardLayout />,
children: [
{ path: "dashboard", element: <DashboardComponent /> },
{ path: "reconciliations", element: <ReconciliationComponent /> },
***{ path: "reconciliationRule/:id", element: <RulesComponent /> },***
{ path: "help", element: <HelpComponent /> },
{ path: "reports", element: <ReportComponent /> },
{ path: "settings", element: <SettingsComponent /> },
{ path: "billing", element: <BillingComponent /> },
{ path: "*", element: <Navigate to="/404" /> },
],
}
]
See if you see I want to send { path: "reconciliationRule/:id", element: } into RulesComponent
Static props I can send easily e.g.
{ path: "reconciliationRule/:id", element: <RulesComponent staticId={1}/> }
I can access this in mapStateToProps, but question is how to pass an ID of URL in react-router-dom.
Tried lot of permutation and combination but unable to solve it.
Don't want to switch to rr5 or rr4 where I can do this using rendering, but I need this to be done rr6.
Any help in this regards will be appreciated.

Resources