Redirection in React Router Dom v6 - javascript

What is the correct process for re-directions in v6? I was previously using the following code in v5, which was working fine:
<Route path="/login">
{user ? <Redirect to="/" /> : <LoginStandard />}
</Route>
However, I would like to use the same logic in this version. When my user has logged in, I would like to re-direct.
<Route path="/login">
<Route index element={<LoginStandard />} />
</Route>

Use the Navigate component to redirect. The conditional rendering logic still needs to be applied and components rendered out on the Route component's element prop.
Example:
<Route
path="/login"
element={user ? <Navigate to="/" replace /> : <LoginStandard />}
/>
It is often considered better practice to abstract this into a custom route protection component that conditionally renders an Outlet for nested routes or the Navigate component.
Example:
import { Navigate, Outlet } from 'react-router-dom';
const AnonymousRoute = ({ user }) => user
? <Navigate to="/" replace />
: <Outlet />;
...
<Route element={<AnonymousRoute user={user} />}>
<Route path="/login" element={<LoginStandard />} />
... other anonymous routes ...
</Route>
... other routes

With React Router Dom v6, you redirect with Navigate and useNavigate instead of Redirect and useHistory used in v5. Something like below. See the comments:
import { Navigate, useNavigate } from "react-router-dom";
export default function Foo() {
const navigate = useNavigate();
// Use navigate returned by useNavigate when you are outside of JSX
navigate("/");
// Use Navigate the imported component when you are inside of JSX
return <Route path="/login">{user ? <Navigate to="/" /> : <LoginStandard />}</Route>;
}

Related

problem with react router dom v6 subroutes

I am doing a practice exercise and I am creating sub-routes with react router dom v6, the problem I have is that the profile route has an authentication if it is authenticated it shows me the profile component otherwise it sends me to home, now to this /profile route I created a /exerciselist subroute but when I want to access /profile/exerciselist the component does not load me, it sends me directly to the /profile route, how can I make it load the profile/exerciselist route?
import React from 'react'
import { BrowserRouter, Route, Routes, Navigate} from "react-router-dom"
import { useContext } from "react"
import { authContext } from "./context/authContext"
import Homepage from "./pages/Homepage"
import Login from "./pages/Login"
import Register from "./pages/Register"
import Notfound from "./pages/Notfound"
import Profile from "./pages/Profile"
import Footer from "./components/footer"
import ExercisesList from './components/exercises_list'
import "./public/css/appStyles/appStyles.css"
function App() {
const { auth } = useContext(authContext)
return (
<BrowserRouter>
<Routes>
<Route path="/" element={!auth.auth ? <Homepage/> : <Navigate to="/profile" replace />}/>
<Route path="/register" element={ !auth.auth ? <Register/> : <Navigate to="/profile" replace />}/>
<Route path="/login" element={ !auth.auth ? <Login /> : <Navigate to="/profile" replace /> } />
<Route path="/profile/*" element={ auth.auth ? <Profile /> : <Navigate to="/" replace /> } >
<Route path="exerciselist" element={<ExercisesList/>} />
</Route>
<Route path="*" element={<Notfound/>}/>
</Routes>
<Footer/>
</BrowserRouter>
);
}
export default App;
When rendering nested routes you have a couple options.
Render an Outlet component in the parent route's component for the nested Route components to be rendered into.
Example:
import { Outlet } from 'react-router-dom';
...
const Profile = () => {
...
return (
<>
... Profile component JSX ...
<Outlet />
</>
);
};
Remove the trailing "*" since the nested route is rendered into Outlet.
<Route
path="/profile"
element={ auth.auth ? <Profile /> : <Navigate to="/" replace />}
>
<Route path="exerciselist" element={<ExercisesList/>} />
</Route>
Render a Routes and nested Route components directly in the routed component.
import { Routes, Route } from 'react-router-dom';
...
const Profile = () => {
...
return (
<>
... Profile component JSX ...
<Routes>
<Route path="exerciselist" element={<ExercisesList/>} />
</Routes>
</>
);
};
...
<Route
path="/profile/*" // <-- trailing * allows matching nested routes
element={ auth.auth ? <Profile /> : <Navigate to="/" replace />}
/>

How to avoid re-initializing component when multiple linked routes use same component

I have a react-router-dom component that was all working fine in v5, and I have since upgraded to v6. In the below router structure, I have a single component that is accessed via different routes. This view has a playback feature that updates the URL when playback is stopped resulting in a foo/<fooId>/<time> url structure, and a means to link to something in the list with bar/<barId>. Both of these render the same component, that uses the params as need be and can handle both entry points.
The issue I'm seeing since moving to v6 is it will refresh if switching from foo/<fooId>/<time> to bar/<barId> or viceversa. Same happens if I click a link to bar/<barId> while on foo/<foodId>/time. It's the same component, and simply updated the browser URL in the past (used to use history.pushState and now use navigate (from useNavigate), I tried adding a replace param, etc., but it still re-mounts the entire component.
How can I avoid this? I can only think to re-structure the pages, but there's a bunch of bookmarks out there that will be very unhappy, haha.
<Route
path="/foo"
element={<AuthedRoute authState={authState} element={<Bar />} />}
>
<Route
path=":fooId"
element={<AuthedRoute authState={authState} element={<Bar />} />}
/>
<Route
path=":fooId/:timestamp"
element={<AuthedRoute authState={authState} element={<Bar />} />}
/>
</Route>
<Route path="/bar">
<Route
path=":barId"
element={<AuthedRoute authState={authState} element={<Bar />} />}
/>
</Route>
AuthRoute:
function AuthedRoute({ authState, element }) {
const location = useLocation()
const { isLoggedIn } = authState
return isLoggedIn ? (
element
) : (
<Navigate
to="/login"
state={{
from: location,
}}
/>
)
}
Similar structure to the example here
Shoot, ok, I hadn't noticed that you had nested "/foo" and "/bar" routes. That changes things a bit. I was able to reproduce the issue of switching between "/foo" and "/bar" routes. I still suggest converting the AuthedRoute into a layout component and rendering an Outlet for nested routes you want to protect.
function AuthedRoute({ authState }) {
const location = useLocation();
const { isLoggedIn } = authState;
return isLoggedIn ? (
<Outlet />
) : (
<Navigate
to="/login"
state={{
from: location
}}
/>
);
}
Next is to configure/structure your routes so the are correctly nested. The following structure seems to work as you are expecting.
<Routes>
<Route element={<AuthedRoute authState={authState} />}>
<Route path="/foo/*">
<Route index element={<Bar />} />
<Route path=":fooId" element={<Bar />} />
<Route path=":fooId/:timestamp" element={<Bar />} />
</Route>
<Route path="/bar/*">
<Route path=":barId" element={<Bar />} />
</Route>
</Route>
<Route path="/login" element={<Login /* auth props? */ />} />
</Routes>
For testing when Bar component was mounted vs rendered (an id prop was passed to each to identify them):
const Bar = ({ id }) => {
useEffect(() => {
console.log("Bar MOUNTED!!", { id });
}, []);
useEffect(() => {
console.log("Bar Rendered!!", { id });
}, [id]);
return <h1>Bar</h1>;
};
After authenticating, use the links to navigate between all the different routes and note when "Mounted" vs "Rendered" logs.
Here's my running codesandbox:

display component across all paths except some in react-router-dom v6

I'm attempting to create a few routes on my web app using react-router. However, some pages need to share components - such as the Navigation or Footer - where others do not.
What I essentially need is a way to check if a path doesn't match a few preset locations, and if it doesn't then render the content.
At the moment I'm doing this like so:
const displayComponentIfAllowed = (location, component) => {
const C = component;
const globalComponentsDisallowedPaths = ["/booking"];
// If the path matches something within the blocked list, then return null.
let allowToRender = true;
globalComponentsDisallowedPaths.forEach(disallowedPath => {
if(location.pathname === disallowedPath){
allowToRender = false;
}
});
// Otherwise, return component to render.
return allowToRender ? <C /> : null;
}
return (
<Router>
<Routes>
<Route render={({ location }) => displayComponentIfAllowed(location, Navigation)} />
<Route path="/">
<Route index element={<Home />} />
<Route path="booking/:customer_id" element={<Booking />} />
</Route>
<Route render={({ location }) => displayComponentIfAllowed(location, Footer)} />
</Routes>
</Router>
);
However, ever since V6 of react-router-dom has been introduced, this doesn't seem to work. I imagine this is because the render prop has been deprecated (although I'm unsure, but there's no mention of it in the docs).
Are there any workarounds - or a better implementation of this which works with V6? Cheers
Create a layout component that renders the UI components you want and an Outlet for nested routes to be rendered into.
Example:
import { Outlet } from 'react-router-dom';
const HeaderFooterLayout = () => (
<>
<Navigation />
<Outlet />
<Footer />
</>
);
...
import {
BrowserRouter as Router,
Routes,
Route
} from "react-router-dom";
...
<Router>
<Routes>
<Route element={<HeaderFooterLayout />} >
<Route path="/">
<Route index element={<Home />} />
... other routes you want to render with header/footer ...
</Route>
</Route>
<Route path="booking/:customer_id" element={<Booking />} />
... other routes you want to not render with header/footer ...
</Routes>
</Router>

React Router always renders NotFound default component

I've been searching all over the internet for solutions but alas I come here for some help. The problem is that the URL changes but the respective component in Route don't render, instead of that, the NotFoundPage is rendered.
Here's the App.js Router code:
<Router history={history}>
<Switch>
<PrivateRoute exact path="/" component={HomePage} />
<Route path="/login" component={LoginPage} />
<Route path="/register" component={RegisterPage} />
<Route component={NotFoundPage}/>
</Switch>
</Router>
Here's PrivateRoute code:
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
export const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
localStorage.getItem('user')
? <Component {...props} />
: <Redirect to={{ pathname: '/login', state: { from: props.location } }} />
)} />
)
The problem I'm facing is the when I go to / the NotFoundPage is rendered instead of LoginPage but the URL correctly redirects and changes to /login. But when I refresh, the LoginPage is rendered. Similarly, when I click on link that takes to /register from LoginPage the URL changes but RegisterPage doesn't render it's the same NotFoundPage.
During contribution on github we figured solution.
This weird behavior flows from using custom history.
const customHistory = createBrowserHistory();
ReactDOM.render(<Router history={customHistory} />, node);
When it is used then somehow router don't react to changes in a location path.
Until you don't add:
<Switch location={window.location}>
According to the docs the location in switch by default is set to window. location but for some reason when you don't use BroswerRouter or StackRouter but just Router this starts working when we directly set location.

Render Same Component With Multiple Paths React Router Dom

I was looking for the simplest way to render the same component but from different paths.
I have the following so that both "/" and "/login" render the Login component.
import React from "react";
import { Route, Switch, Redirect } from 'react-router-dom';
import './App.scss';
import Login from "../../login/Login";
const App = () => {
return (
<div className="App">
<div className="App-container">
<Switch>
<Route exact path={["/", "/login"]} component={() =>
<Login login={true} />}/>
<Redirect to="/" />
</Switch>
</div>
</div>
);
}
export default App;
This does appear to work, however, it returns an error in the console.
Warning: Failed prop type: Invalid prop 'path' of type 'array' supplied to 'Route', expected 'string'.
I'm trying to do this...
<Route exact path={"/"} component={() => <Login login={true} />}/>
<Route exact path={"/login"} component={() => <Login login={true} />}/>
But with a shorter method, is this possible with react-router-dom? Any help would be greatly appreciated
You could create an array that contains the paths / and /login and use map on that array to render the same thing for both paths.
<Switch>
{["/", "/login"].map(path => (
<Route
key={path}
exact
path={path}
render={() => <Login login={true} />}
/>
))}
<Redirect to="/" />
</Switch>
If you wish to render the same component on the several routes, you can do this by specifying your path as a regular expression
lets say you want to display 'Home' component for 'Home', 'User' and 'Contact' components then here is code.
<Route path="/(home|users|contact)/" component={Home} />

Categories

Resources