The problem is that when I start using React.lazy, input styles are not rendering correctly. With lazy I get default Antd input styles, not mine. What's the problem and how I can fix it? You can see my code and its results in the pictures below. Thanks!
This picture shows styles that this component should render
This picture shows what styles are applied when using lazy
Code with lazy
import { lazy, Suspense } from 'react';
import { Routes, Route, Navigate } from 'react-router-dom';
import { useAppSelector } from '../../../hooks/redux-hooks';
import { selectCurrentUser } from '../../../store/slices/user/userSelectors';
import { Spinner } from '../../common/Spinner/Spinner';
const HomePage = lazy(() => import('../../../pages/HomePage'));
const ShopPage = lazy(() => import('../../../pages/ShopPage'));
const CheckoutPage = lazy(() => import('../../../pages/CheckoutPage'));
const AuthPage = lazy(() => import('../../../pages/AuthPage'));
export const AppRoutes = () => {
const currentUser = useAppSelector(selectCurrentUser);
return (
<Suspense fallback={<Spinner />}>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="shop/*" element={<ShopPage />} />
<Route path="checkout" element={<CheckoutPage />} />
<Route
path="auth"
element={currentUser ? <Navigate to="/" /> : <AuthPage />}
/>
</Routes>
</Suspense>
);
};
Code without lazy
import { Routes, Route, Navigate } from 'react-router-dom';
import { useAppSelector } from '../../../hooks/redux-hooks';
import { selectCurrentUser } from '../../../store/slices/user/userSelectors';
import HomePage from '../../../pages/HomePage';
import ShopPage from '../../../pages/ShopPage';
import AuthPage from '../../../pages/AuthPage';
import CheckoutPage from '../../../pages/CheckoutPage';
export const AppRoutes = () => {
const currentUser = useAppSelector(selectCurrentUser);
return (
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="shop/*" element={<ShopPage />} />
<Route path="checkout" element={<CheckoutPage />} />
<Route
path="auth"
element={currentUser ? <Navigate to="/" /> : <AuthPage />}
/>
</Routes>
);
};
Related
Testing a router component and when I call the screen.debug() in a test after rendering, the DOM output is not what I expected. Why?
Test:
import { render, userEvent as user, screen, getByRole } from '#testing-library/react'
import { Router } from 'react-router-dom'
import { createMemoryHistory } from 'history'
import AppRouter from '../router'
test('Renders AppRouter', () => {
const history = createMemoryHistory({ initialEntries: ['/post'] })
render(() => (
<Router history={history}>
<AppRouter />
</Router>
))
screen.debug()
})
Component:
import { BrowserRouter, Switch, Route, Redirect } from 'react-router-dom'
import { useState } from 'react'
import useLocalStorage from './hooks/useLocalStorage'
import * as Constants from './constants'
import Header from './layout/header/header'
import MainPage from './pages/mainPage/mainPage'
import PostPage from './pages/postPage/postPage'
import UserPage from './pages/userPage/userPage'
import LoginPage from './pages/loginPage/loginPage'
import SignupPage from './pages/signupPage/signupPage'
import NewPage from './pages/newPage/newPage'
import FeedbackPage from './pages/feedbackPage/feedbackPage'
import AdminPage from './pages/adminPage/adminPage'
import SettingPage from './pages/settingPage/settingPage'
import { WebContext } from './context/WebContext'
import Favicon from 'react-favicon'
const AppRouter = () => {
const [adminCode, setAdminCode] = useLocalStorage('admin', '')
const [isMenuOpen, setIsMenuOpen] = useState(false)
const [page, setPage] = useState(Constants.Page.Home)
return (
<BrowserRouter>
<div role="hello">
<Favicon url={require('../public/favicon.ico')} />
<WebContext.Provider
value={{
isMenuOpen,
setIsMenuOpen,
page,
setPage,
adminCode,
setAdminCode,
}}
>
<Header />
<h1>
hello
</h1>
<Switch>
<Route component={MainPage} path="/" exact={true} />
<Route component={PostPage} path="/post/:id" />
<Route component={UserPage} path="/user" />
<Route component={LoginPage} path="/login" />
<Route component={SignupPage} path="/signup" />
<Route component={NewPage} path="/new" />
<Route component={FeedbackPage} path="/feedback" />
<Route component={AdminPage} path="/admin" />
<Route component={SettingPage} path="/setting" />
<Route component={() => <Redirect to="/" />} />
</Switch>
</WebContext.Provider>
</div>
</BrowserRouter>
)
}
export default AppRouter
Code-Trace:
EDIT
Error when not passing in a function to render:
Favicon error:
You are passing a function to the test render function when it's expecting JSX.
Remove the function definition and just pass the Router and AppRouter as JSX.
Example:
test('Renders AppRouter', () => {
const history = createMemoryHistory({ initialEntries: ['/post'] });
render(
<Router history={history}>
<AppRouter />
</Router>
);
screen.debug();
});
Below is my App.js code and I am trying to redirect the app to '/companies' (Companies element) only ONCE on the first time the app loads. Right now, it is not redirecting/navigating... Is there a better way to accomplish this?
App.js
import React, { useEffect, useState } from 'react';
import { Routes, Route, BrowserRouter as Router, Navigate } from 'react-router-dom';
import { port } from './variables/global';
import "./assets/css/mdb.css";
import Companies from './components/Companies';
import Company from './components/Company';
// import Settings from './components/UserSettings';
import NavbarDev from './components/NavbarDev';
import WebFont from 'webfontloader';
WebFont.load({
google: {
families: ['Barlow', 'Playfair Display', 'Overpass']
}
});
function CompaniesRouter() {
return (
<Routes>
<Route path="/" element={<Companies />} />
<Route path="/last" element={<Company />} />
</Routes>
)
}
function Test() {
const [firstRender, setFirstRender] = useState(true);
let el = (
<React.Fragment>
<h1>hellooooo</h1>
</React.Fragment>
)
// redirect only once
useEffect(() => {
if (process.env.NODE_ENV === 'development' && firstRender) {
el = <Navigate replace to='/companies' />
setFirstRender(false)
}
}, [firstRender])
return el
}
export default ({ history }) => {
console.log('app', history.location.pathname)
let whatPort = location.port;
return (
<Router history={history}>
{(process.env.NODE_ENV === 'development' && whatPort === port.toString()) ? <NavbarDev /> : ''}
<Routes>
<Route path="/companies/*" element={<CompaniesRouter />} />
<Route path="/user/settings" element={<Companies />} />
<Route path='/' element={<Test />} />
<Route
path="*"
element={<Navigate to="/" replace />}
/>
</Routes>
</Router>
);
};
The useEffect shouldn't use a variable that indicates true if you want to execute it only one time, you can just leave the array empty like this
import React, { useEffect, useState } from 'react';
import { Routes, Route, BrowserRouter as Router, Navigate, useNavigate } from 'react-router-dom';
import { port } from './variables/global';
import "./assets/css/mdb.css";
import Companies from './components/Companies';
import Company from './components/Company';
// import Settings from './components/UserSettings';
import NavbarDev from './components/NavbarDev';
import WebFont from 'webfontloader';
WebFont.load({
google: {
families: ['Barlow', 'Playfair Display', 'Overpass']
}
});
function CompaniesRouter() {
return (
<Routes>
<Route path="/" element={<Companies />} />
<Route path="/last" element={<Company />} />
</Routes>
)
}
export default ({ history }) => {
console.log('app', history.location.pathname)
let whatPort = location.port;
useEffect(() => {
const navigate = useNavigate();
navigate("/companies");
},[])
return (
<Router history={history}>
{(process.env.NODE_ENV === 'development' && whatPort === port.toString()) ? <NavbarDev /> : ''}
<Routes>
<Route path="/companies/*" element={<CompaniesRouter />} />
<Route path="/user/settings" element={<Companies />} />
<Route path='/' element={<Test />} />
<Route
path="*"
element={<Navigate to="/" replace />}
/>
</Routes>
</Router>
);
};
The answer that worked for me is to not use useEffect and to set firstRender to false after Test loads the first time
** App.js **
import React, { useEffect, useState } from 'react';
import { Routes, Route, BrowserRouter as Router, Navigate, useNavigate } from 'react-router-dom';
import { port } from './variables/global';
import "./assets/css/mdb.css";
import Companies from './components/Companies';
import Company from './components/Company';
import NavbarDev from './components/NavbarDev';
import WebFont from 'webfontloader';
WebFont.load({
google: {
families: ['Barlow', 'Playfair Display', 'Overpass']
}
});
function CompaniesRouter() {
return (
<Routes>
<Route path="/" element={<Companies />} />
<Route path="/last" element={<Company />} />
</Routes>
)
}
function Test({ firstRender, setFirstRender }) {
let el = (
<React.Fragment>
<h1>hellooooo</h1>
</React.Fragment>
)
console.log('render', firstRender)
// redirect only once
if (process.env.NODE_ENV === 'development' && firstRender) {
el = <Navigate replace to='/companies/last' />
setFirstRender(false);
}
return el
}
export default ({ history }) => {
console.log('app', history.location.pathname)
let whatPort = location.port;
return (
<Router history={history}>
{(process.env.NODE_ENV === 'development' && whatPort === port.toString()) ? <NavbarDev /> : ''}
<Routes>
<Route path="/companies/*" element={<CompaniesRouter />} />
<Route path="/user/settings" element={<Companies />} />
<Route path='/' element={<Test />} />
<Route
path="*"
element={<Navigate to="/" replace />}
/>
</Routes>
</Router>
);
};
I want authenticated routes if user is not logged in the page should not be accessible like if someone enters in the url localhost.../admin/dashboard he should not be able to navigate instead he should be taken to signin page if not logged in.
I'm using react-router v6 and creating private routes for my application.
AdminRoute.js File Code is below
import React from "react";
import { Route, Navigate} from 'react-router-dom';
import { isAuthenticated } from "../helper/auth";
//props component is assigned to Component
//...rest spreading props property but reassigning it to a variable called rest
const AdminRoute = ({ component: Component, ...rest }) => {
return (
<Route
{...rest}
render={(props) =>
isAuthenticated() && isAuthenticated().role === 1 ? (
<Component {...props} />
) : (
<Navigate to = '/signin' />
)
}
/>
)
};
export default AdminRoute;
App.js File Code is below
import React from 'react';
import {BrowserRouter, Route, Routes} from 'react-router-dom';
import Header from './Header';
import Home from './Home';
import Signup from './Signup';
import Signin from './Signin';
import ForgotPassword from './forgot-password';
import UserDashboard from './UserDashboard';
import AdminDashboard from './AdminDashboard';
import ShowroomDashboard from './ShowroomDashboard';
import AdminRoute from './AdminRoute';
import NotFound from './NotFound';
const App = () => (<BrowserRouter>
<Header />
<main>
<Routes>
<Route exact path='/' element={<Home />} />
<Route exact path='/signup' element={<Signup />} />
<Route exact path='/signin' element={<Signin />} />
<Route exact path='/forgotpassword' element={<ForgotPassword />} />
<Route exact path='/user/dashboard' element={<UserDashboard />} />
<AdminRoute exact path='/admin/dashboard' element={<AdminDashboard />} />
<Route exact path='/showroom/dashboard' element={<ShowroomDashboard />} />
<Route exact path = '*' element={<NotFound />} />
</Routes>
</main>
</BrowserRouter>
);
export default App;
react-router-dom no longer supports custom route components, preferring now component wrappers that handle the auth logic and render either the children prop or an Outlet for nested routes, or the redirect.
Wrap a single "Route" component:
import React from "react";
import { Navigate } from 'react-router-dom';
import { isAuthenticated } from "../helper/auth";
const AdminRoute = ({ children }) => {
return isAuthenticated()?.role === 1
? children
: <Navigate to='/signin' replace />;
};
...
<Route
path='/admin/dashboard'
element={(
<AuthRoute>
<AdminDashboard />
</AuthRoute>
)}
/>
Wrap nested Route components:
import React from "react";
import { Navigate, Outlet } from 'react-router-dom';
import { isAuthenticated } from "../helper/auth";
const AdminWrapper = () => {
return isAuthenticated()?.role === 1
? <Outlet />
: <Navigate to='/signin' replace />;
};
...
<Route path='/admin/dashboard/*' element={<AdminWrapper />}>
<Route index element={<AdminDashboard />} />
... any other '/admin/dashboard/*' routes ...
</Route>
I have main react application which I want to split to 2 applications - using only one host. The split should be performed using routes.
This is my my main app routing:
import React, { Suspense } from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import DesktopApp from './components/DesktopApp';
import MobileApp from './components/MobileApp';
import classes from './App.module.scss';
interface Props { }
const AppView: React.FC<Props> = (props: React.PropsWithChildren<Props>) => {
return (
<BrowserRouter>
<Suspense fallback={null}>
<Switch>
<Route path="/mobile" component={MobileApp} />
<Route path="**" component={DesktopApp} />
</Switch>
</Suspense>
</BrowserRouter>
);
};
AppView.displayName = 'AppView';
AppView.defaultProps = {};
export default React.memo(AppView);
This is my MobileApp component:
import React, { Suspense } from 'react';
import { Route, Switch, Redirect } from 'react-router-dom';
import classes from './MobileApp.module.scss';
const ScanPayment = React.lazy(() => import('./pages/mobile/ScanPayment/ScanPayment'));
interface Props { }
const MobileAppView: React.FC<Props> = (props: React.PropsWithChildren<Props>) => {
return (
<Suspense fallback={null}>
<Switch>
<Route path="/mobile/scan-payment"></Route>
<Redirect path="**" to="/mobile/scan-payment" />
</Switch>
</Suspense>
);
};
MobileAppView.displayName = 'MobileAppView';
MobileAppView.defaultProps = {};
export default React.memo(MobileAppView);
DesktopApp component (please focus the routes - the rest are really irrelevant):
import React, { Suspense } from 'react';
import { Route, Switch, Redirect } from 'react-router-dom';
import AuthIntro from './pages/desktop/AuthIntro/AuthIntro';
import SideNav from './pages/desktop/SideNav/SideNav';
import Nav from './pages/desktop/Nav/Nav';
import { IUser } from '../models/user';
import classes from './DesktopApp.module.scss';
const Login = React.lazy(() => import('./pages/desktop/Login/Login'));
const Register = React.lazy(() => import('./pages/desktop/Register/Register'));
const ForgotPassword = React.lazy(() => import('./pages/desktop/ForgotPassword/ForgotPassword'));
const Dashboard = React.lazy(() => import('./pages/desktop/Dashboard/Dashboard'));
const UpdateDetails = React.lazy(() => import('./pages/desktop/UpdateDetails/UpdateDetails'));
const RepresentativesList = React.lazy(() => import('./pages/desktop/RepresentativesList/RepresentativesList'));
const AddRepresentative = React.lazy(() => import('./pages/desktop/AddRepresentative/AddRepresentative'));
const UpdateRepresentative = React.lazy(() => import('./pages/desktop/UpdateRepresentative/UpdateRepresentative'));
const NewPayment = React.lazy(() => import('./pages/desktop/NewPayment/NewPayment'));
interface Props {
loggedIn: boolean | null;
user: IUser | null;
}
const DesktopAppView: React.FC<Props> = (props: React.PropsWithChildren<Props>) => {
return (
<Suspense fallback={null}>
{props.loggedIn === false && (
<React.Fragment>
<AuthIntro />
<Switch>
<Route path="/login" component={Login} />
<Route path="/register" component={Register} />
<Route path="/forgot-password" component={ForgotPassword} />
<Redirect path="**" to="/login" />
</Switch>
</React.Fragment>
)}
{props.loggedIn && (
<React.Fragment>
<SideNav />
<div className={classes['container']}>
<Nav
officeName={props.user!.officeName}
BNNumber={props.user!.BNNumber}
/>
<Switch>
<Route path="/dashboard" component={Dashboard} />
<Route path="/update-details" component={UpdateDetails} />
<Route path="/representatives-list" component={RepresentativesList} />
<Route path="/add-representative" component={AddRepresentative} />
<Route path="/update-representative/:id" component={UpdateRepresentative} />
<Route path="/new-payment" component={NewPayment} />
<Redirect path="**" to="/dashboard" />
</Switch>
</div>
</React.Fragment>
)}
</Suspense>
);
};
DesktopAppView.displayName = 'DesktopAppView';
DesktopAppView.defaultProps = {};
export default React.memo(DesktopAppView);
Accessing the desktop routes is working great, but I can't access the mobile routes.
When I go to the url http://localhost:4200/mobile/scan-payment I see blank page. no HTML at all (but of course there should be HTML code)..
Any help?
You missing property component for the <Route /> in MobileView:
import React, { Suspense } from 'react';
import { Route, Switch, Redirect } from 'react-router-dom';
import classes from './MobileApp.module.scss';
const ScanPayment = React.lazy(() => import('./pages/mobile/ScanPayment/ScanPayment'));
interface Props { }
const MobileAppView: React.FC<Props> = (props: React.PropsWithChildren<Props>) => {
return (
<Suspense fallback={null}>
<Switch>
<Route path="/mobile/scan-payment" component={ScanPayments}></Route>
<Redirect path="**" to="/mobile/scan-payment" />
</Switch>
</Suspense>
);
};
MobileAppView.displayName = 'MobileAppView';
MobileAppView.defaultProps = {};
export default React.memo(MobileAppView);
In AppRouter, I have a conditional route with redirect for <AdminLayout/>.
relevant snippet:
<Route
exact
path="/admin"
strict
render={(props) => <AdminLayout {...props} />}
>
{loggedIn ? <Redirect to="/admin/summary" /> : <Login />}
</Route>
If loggedIn is true then, redirect to /admin/summary else redirect it back to <Login/>
The problem is: it is only changing the URL but not rendering the <AdminLayout/>.
Not sure where I am going wrong and what I am missing.
UPDATED PrivateRoute and AppRouter below
AppRouter
import React, { useEffect } from "react";
import { Router, Route, Switch, Redirect } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import { createBrowserHistory } from "history";
import { alertActions } from "../actions";
import { history } from "../helpers";
import AdminLayout from "layouts/Admin/Admin.js";
import AuthLayout from "layouts/Auth/Auth.js";
import ResetPassword from "../components/pages/reset-password/ResetPassword";
import MailReset from "../components/pages/reset-password/MailReset";
import PrivateRoute from "../routes/PrivateRoute";
import Dashboard from "views/Dashboard";
const hist = createBrowserHistory();
const AppRouter = () => {
const alert = useSelector((state) => state.alert);
const dispatch = useDispatch();
useEffect(() => {
history.listen((location, action) => {
// clear alert on location change
dispatch(alertActions.clear());
});
}, []);
return (
<Router history={hist}>
<Switch>
{/* <Route path="/admin" render={(props) => <AdminLayout {...props} />} /> */}
<PrivateRoute exact path="/admin">
<Dashboard />
</PrivateRoute>
<Route
path="/auth/login"
render={(props) => <AuthLayout {...props} />}
/>
<Route exact path="/auth/forgotPassword" component={ResetPassword} />
<Route exact path="/auth/mail_reset" component={MailReset} />
<Redirect from="*" to="/auth/login" />
</Switch>
</Router>
);
};
export default AppRouter;
PrivateRoute
import React from "react";
import { Route, Redirect } from "react-router-dom";
import AdminLayout from "../layouts/Admin/Admin";
function PrivateRoute({ component: Component, roles, ...rest }) {
console.log("rest pvt route", ...rest);
return (
<Route
{...rest}
render={(props) => {
console.log("propsssss", props);
// if (!localStorage.getItem('userid')) {
if (!localStorage.getItem("access_token")) {
// not logged in so redirect to login page with the return url
return (
<Redirect
to={{ pathname: "/auth/login", state: { from: props.location } }}
/>
);
}
// logged in so return component
return <AdminLayout {...props} />;
}}
/>
);
}
export default { PrivateRoute };
So trying to explain what its is wrong:
You are setting rendering child and render props that's why children props takes priority here:
<Route
exact
path="/admin"
render={(props) => <AdminLayout {...props} />}
>
{loggedIn ? <Redirect to="/admin/summary" /> : <Login />}
</Route>
Your private route is correct but need to add your layout as well:
return <AdminLayout {...props} /><Component {...props} /></AdminLayout/>;
Inside app route you need to import PrivateRoute component it will look like this:
import PrivateRoute from './PrivateRoute';
const AppRouter = () => {
const alert = useSelector((state) => state.alert);
const loggedIn = useSelector((state) => state.authentication.loggedIn);
const dispatch = useDispatch();
useEffect(() => {
history.listen((location, action) => {
// clear alert on location change
dispatch(alertActions.clear());
});
}, []);
return (
<Router history={hist}>
<Switch>
<PrivateRoute exact path='/admin'>
<YOUR AUTH COMPONENT WHICH YOU WANT TO RENDER />
</PrivateRoute>
<Route
path='/auth/login'
render={(props) => <AuthLayout {...props} />}
/>
<Route exact path='/auth/forgotPassword' component={ResetPassword} />
<Route exact path='/auth/mail_reset' component={MailReset} />
<Redirect from='*' to='/auth/login' />
</Switch>
</Router>
);
};
Here I created demo code of this. Take reference from it: https://codesandbox.io/s/react-router-redirects-auth-forked-6q6o4?file=/example.js