Create a modal route using React.lazy() component in React Router V6 - reactjs

Is it possible to create a modal route with lazy component pages? By that I mean the <Modal/> is rendered inside the <User/> page instead of being a page of its own.
A modal route is working perfectly with elements:
<Routes>
<Route path="user" element={<User/>}>
<Route path="modal" element={<Modal/>} />
</Route>
</Routes>
But when I use lazy components, the <Modal/> is not rendered inside <User/>. At /user/modal, the <User/> content is gone and the <Modal/> is rendered as a new page:
const User = lazy(() => System.import("./page/User"));
const Modal = lazy(() => System.import("./page/Modal"));
<Routes>
<Route path="user">
<User/>
<Route path="modal">
<Modal/>
</Route>
</Route>
</Routes>
I have the <Outlet/> inside the User Page as per the preview article:
function User = ()=>{
return (
<div><Link to="modal">modal</Link><Outlet/></div>
)
}

Related

React router route loader not working on nested components

Am using react router v6 and i would like to use the new loader to load the data before the component loads. So i have the following
In my index.js
const router = createBrowserRouter(
createRoutesFromElements(
<Route path="*"
loader={async ({ params }) => {
console.log("index loader log"); //this logs
return true;
}}
element={<App />}
> </Route>
)
);
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<RouterProvider router={router} />
);
in my app component i have nested routes like
const App = () => {
return (
<>
<Routes>
<Route path="auth/*" element={<AuthLayout/>}/>
<Route path="about"
loader={async ({ params }) => {
console.log("about child loader log"); //this doesnt log
return true;
}}
element={<AboutPage/>}/>
</Routes>
<h1>Testing app</h1>
</>
);
}
On the app component the loader on the Route path="about" does not console.log when i visit the about route but the component is rendered. What am i missing for the loader to work on the child route.
Based on some basic testing it seems that in order for the new RRDv6.4 data APIs to work you need to specify the complete routing configuration in the createBrowserRouter function.
There does however appear to already be an issue filed with #remix-run/react-router for this behavior as a reported bug, so you may want to follow it if it ever addressed/resolved. (I suspect it was you since the name is "geoffrey" and the timing is coincidentally about an hour ago around the same time as this post)
This above issue has since been closed with comment:
Descendant <Routes> trees do not participate in data loading
(https://reactrouter.com/en/main/components/routes) since they cannot
be known ahead of render-time. You'll need to lift your descendant
route definitions up into the routes you pass to createBrowserRouter.
The relevant information regarding the descendent routes and the new Data API can be found in the Routes documentation in a note.
Note:
If you're using a data router like createBrowserRouter it is
uncommon to use this component as it does not participate in data
loading.
Hoist the entire route declaration to the parent creating the data router. The following does work with the loader function for the "/about" route and About component.
const router = createBrowserRouter(
createRoutesFromElements(
<Route
path="*"
loader={({ params }) => {
console.log("index loader log");
return "This is the App";
}}
element={<App />}
>
<Route path="auth/*" element={<AuthLayout />} />
<Route
path="about"
loader={({ params }) => {
console.log("about child loader log");
return "this is the about page";
}}
element={<AboutPage />}
/>
</Route>
)
);
The App component should render an Outlet for the nested routes to render their content into.
import { Outlet } from 'react-router-dom';
const App = () => {
return (
<>
<h1>Testing app</h1>
<Outlet />
</>
);
};

How to hide a component in all nested routes with React router dom V6?

I'd like to hide a component depending on the route with react-router-dom v6. I could make it with exact path, but I'd also like to hide it in nested routes. I tried like this:
{location !== "/dashboard/*" && <Header />}
But it works only on the page http://localhost:3000/dashboard/*, which is not what I want. How could I fix this?
This is the full code:
const location = useLocation().pathname
const conditionLocation = location === "/dashboard" || location === "/dashboard/*"
return !conditionLocation && <Header />
Routes:
// Packages
import React from "react"
import { Routes, Route } from "react-router-dom"
// User
import Dashboard from "../pages/admin/Dashboard"
import EditAccount from "../pages/admin/EditAccount"
function Switch() {
return (
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/dashboard/edit" element={<EditAccount />} />
</Routes>
)
}
export default Switch
Thanks for your help!
Fix this issue using javascript regex:-
{!(location.match(/^\/dashboard.*$/gim)) && <Header />}
Hope that works..
From what I understand, you are wanting to render a Header component only on specific routes. You've not shared much context, but I understand you don't want to render the Header on any "/dashboard" path. You can achieve this with layout and Outlet components.
const HeaderLayout = () => (
<>
<Header />
<Outlet /> // <-- wrapped Route components render into outlet
</>
);
For the routes you want to render with the Header component render them into a route rendering the HeaderLayout.
<Routes>
<Route element={<HeaderLayout />}>
..... routes render into outlet with header
</Route>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/dashboard/edit" element={<EditAccount />} />
</Routes>

Protected routes for react router v5 isn't working

I'm trying to create a protected/private route with react-router-dom v5, as in the link here, I use only the code I needed - Protected routes and authentication with React Router v5.
Now I need both components private from each other
The problem: after Getting Home component transferred fromSignUp components I can go back to SignUp and thats not what I want.
Signup corrently is the first page (as sign-in). I don't want the user to go back to sigh-up components if he already signed up and present at the Home component.
The working website project is here - CodeSandbox
Anyone has idea how can I make both component protected/private?
GuardedRoute.jsx
function GuardedRoute({ children, auth, ...rest }) {
return (
<Route
{...rest}
render={() => {
return auth === true ? children : <Redirect to="/signup" />;
}}
/>
);
}
export default GuardedRoute;
App.js
const App = () => {
const [userID, setUserID] = useState('');
const [userSignedUp, setIfSignUp] = useState(false);
return (
<div className="App-div">
<GuardedRoute exact path="/home" auth={userSignedUp}>
<Home userIDNumber={userID} setIfSignUp={setIfSignUp} />
</GuardedRoute>
<Switch>
<Route path="/signup">
<SignUp setUserNumber={setUserID} setIfSignUp={setIfSignUp} />
</Route>
</Switch>
</div>
);
};
export default App;
Please try any of your solutions at my codesandbox before posting your answer so we will not try solutions in theory only and in vain :)
You could make the signup route only exist if the user is not logged in, and then use a catch-all that will redirect to /home
<div className="App-div">
<Switch>
{!userSignedUp && (
<Route path="/signup">
<SignUp setUserNumber={setUserID} setIfSignUp={setIfSignUp} />
</Route>
)}
<GuardedRoute path="/home" auth={userSignedUp}>
<Home userIDNumber={userID} setIfSignUp={setIfSignUp} />
</GuardedRoute>
<Route path="/">
<Redirect to="/home" />
</Route>
</Switch>
</div>
Updated sample: https://codesandbox.io/s/jolly-https-t7dmj?file=/src/containers/App.js
or you could encapsulate the logic to another component like GuardedRoute, let say UnguardedRoute that will redirect somewhere if user is logged in.

Is there a way to place importance in which route to be displayed first?

<div className="app">
<Router>
{/* If there is no user, render the login screen else render the app components */}
{!user ? (
<LoginScreen/>
) : (!plan ? (
<ProfileScreen/>
) : (
<Switch>
<Route exact path = '/'>
<Watching/>
</Route>
<Route exact path ="/home">
<HomeScreen/>
</Route>
<Route exact path='/profile'>
<ProfileScreen/>
</Route>
<Route exact path='/ManageProfiles'>
<ManageProfiles/>
</Route>
</Switch>
))
}
</Router>
</div>
In my App.js, these are the my routes components. Upon logging in, the route component with path /profile will be displayed immediately instead of /home. Is there a way to ensure that everytime i log in, it would redirect me to /home?
You can use useNavigate hook and redirect your user by using it in your Login handler like that:
//Inside your component
const navigate = useNavigate()
yourLoginHandler() {
...othercode
navigate("/home")
}
Simple, just after you login, you navigate the route to /home. You can use history for that.
import { useHistory } from "react-router";
const history = useHistory();
login().then(() => history.push('/home'));

Adding MsalAuthenticationTemplate for some routes causes all routes to require login

I’ve got a ReactJS website in which I am trying to use "#azure/msal-react": "^1.0.0-beta.1", and ran into some issues that have me flummoxed.
Most of my pages are open to the public. Some require login. If I add the MSALAuthenticationTemplate as below (but with interactionType=Redirect), as soon as I start the site, it asks me to login. I thought it would only do that if I hit a route that was in the AuthenticationTemplate.
Using InteractionType Popup causes the SPA to throw an exception on startup
Error: Objects are not valid as a React child (found: object with keys {login, result, error}). If you meant to render a collection of children, use an array instead. in p (at App.tsx:44)
All of my routes are, for some reason, coming back to the home page instead of loading the relevant components, event with the AuthenticationTemplate commented out.
I had this pretty much working using straight Javascript, but was running into ESLint issues when publishing, so I thought Typescript would help me fix those. But now it’s just broke.
render() {
initializeIcons();
return (
<MsalProvider instance={msalClient} >
<div className="d-flex flex-column h-100">
<TopMenu />
<div className="container-fluid flex-grow-1 d-flex">
<div className="row flex-fill flex-column flex-sm-row">
<BrowserRouter>
<MsalAuthenticationTemplate
interactionType={InteractionType.Popup}
errorComponent={this.ErrorComponent}
loadingComponent={this.LoadingComponent}>
<Switch>
<Route path="/addevent">
<AddEvent />
</Route>
<Route path="/mydashboard">
<MyDashboard />
</Route>
</Switch>
</MsalAuthenticationTemplate >
<UnauthenticatedTemplate>
<Switch>
<Route path='/'>
<Home />
</Route>
<Route path="/about">
<About />
</Route>
<Route path="/contactus">
<ContactUs />
</Route>
<Route path="/faq">
<Faq />
</Route>
<Route path="/fetchevents">
<FetchEvents />
</Route>
<Route path="/gettingstarted">
<GettingStarted />
</Route>
<Route path="/partners">
<Partners />
</Route>
<Route path="/privacypolicy">
<PrivacyPolicy />
</Route>
<Route path="/sponsors">
<Sponsors />
</Route>
<Route path="/termsofservice">
<TermsOfService />
</Route>
<Route path="/userstories">
<UserStories />
</Route>
</Switch>
</UnauthenticatedTemplate>
<div>
<Footer />
</div>
</BrowserRouter>
</div>
</div>
</div>
</MsalProvider>
);
Let's start with the UnauthenticatedTemplate. If the user is authenticated, children of the component will not show. So I guess you don't want to use it there. It's typical usage is for Login/Logout button for example.
Another problem is that if you are using MsalAuthenticationTemplate as the parent of the Switch and Route components. The problem is that you are guarding switch and routes from unauthenticated users, but this components should always be available without authentication, if you don't want to protect whole page.
During rendering React will go through your components one by one and first child of the BrowserRouter component it will try to render is MsalAuthenticationTemplate and since user is not authenticated, it will redirect user to login page.
This is quote from react-router docs:
A Route is always technically “rendered” even though it’s rendering null. When the 's path matches the current URL, it renders its children (your component).
Because of this the children of the route will only be rendered if the route will be hit. So you need to put MsalAuthenticationTemplate component as a direct child of the route, or even inside such component:
<Switch>
<Route path="/addevent">
<MsalAuthenticationTemplate
interactionType={InteractionType.Redirect}
authenticationRequest={loginRequest}
>
<AddEvent />
</MsalAuthenticationTemplate>
</Route>
...
</Switch>
As for all the webpages redirected to your home screen, you should add exact keyword to your Home route. This attribute causes it to not match all other routes also. Single '/' matches all your other routes.
In addition to the answer already provided, there is a way (cleaner in my opinion) you can configure MSAL react to take advantage of the router's navigate functions when MSAL redirects between pages in your app.
Here is how it works:
In your index.js file you can have something like so:
import { PublicClientApplication, EventType } from "#azure/msal-browser";
import { msalConfig } from "./authConfig";
export const msalInstance = new PublicClientApplication(msalConfig);
ReactDOM.render(
<React.StrictMode>
<Router>
<ThemeProvider theme={theme}>
<App pca={msalInstance} />
</ThemeProvider>
</Router>
</React.StrictMode>,
document.getElementById('root')
);
As shown, you need to pass msal instance as props to your main App.
Then in your App.js where you setup your routes, you will need to do the following:
import { MsalProvider } from "#azure/msal-react";
import { CustomNavigationClient } from "NavigationClient";
import { useHistory } from "react-router-dom";
function App({ pca }) {
// The 3 lines of code below allow you to configure MSAL to take advantage of the router's navigate functions when MSAL redirects between pages in your app
const history = useHistory();
const navigationClient = new CustomNavigationClient(history);
pca.setNavigationClient(navigationClient);
return (
<MsalProvider instance={pca}>
<Grid container justify="center">
<Pages />
</Grid>
</MsalProvider>
);
}
function Pages() {
return (
<Switch>
<Route path="/addevent">
<AddEvent />
</Route>
<Route path="/mydashboard">
<MyDashboard />
</Route>
<Route path='/'>
<Home />
</Route>
<Route path="/about">
<About />
</Route>
<Route path="/contactus">
<ContactUs />
</Route>
<Route path="/faq">
<Faq />
</Route>
// your other routes
</Switch>
)
}
And here is the helper function used in App.js that enables navigation by overriding the the default function used by MSAL
import { NavigationClient } from "#azure/msal-browser";
/**
* This is an example for overriding the default function MSAL uses to navigate to other urls in your webpage
*/
export class CustomNavigationClient extends NavigationClient{
constructor(history) {
super();
this.history = history;
}
/**
* Navigates to other pages within the same web application
* You can use the useHistory hook provided by react-router-dom to take advantage of client-side routing
* #param url
* #param options
*/
async navigateInternal(url, options) {
const relativePath = url.replace(window.location.origin, '');
if (options.noHistory) {
this.history.replace(relativePath);
} else {
this.history.push(relativePath);
}
return false;
}
}
You can then use AuthenticatedTemplate on your private pages and UnauthenticatedTemplate on the public pages. For example if you have have addEvent.js (private) and Home.js (public), you will have each components like so:
export function Home() {
return (
<>
<AuthenticatedTemplate>
<p>Welcome Home - it's public</p>
</AuthenticatedTemplate>
</>
);
}
export function AddEvent() {
return (
<>
<UnauthenticatedTemplate>
<Typography variant="h6">
Add event - it is a private page
</Typography>
</UnauthenticatedTemplate>
</>
);
}
Here is a complete example on how to use react-router with msal react for your reference.

Resources