React Router v6 browser refresh and url bar problem - javascript

I'm using react-router#6 and createBrowseRouter. Navigating via links on page works well and the URL is updating, but when refreshing or trying to access the path directly, the user is sent to the index route.
My basic router structure looks like this:
Routes.js
const router = createBrowserRouter(
createRoutesFromElements(
<Route
path="/"
element={
<Root
...some props
/>
}
loader={some loader...}
action={some action...}
>
<Route index element={<Home />} exact />
<Route path="templates/:id">
<Route
index
element={<Document />}
errorElement={some error element...}
loader={some loader...}
action={some action...}
/>
<Route
path="edit"
element={
<EditDocument ...some props />
}
loader={some loader...}
action={some action...}
/>
</Route>
<Route path="settings" element={<Settings />} />
</Route>
)
)
So basically a root route with some nested routes. At the bottom of this component, I'm returning like this: return <RouterProvider router={router} />;
The Routes component in the Routes.js is rendered via an App.js, in which I'm loading some data and passing that data via props (to the Routes component). The App.js is rendered via index.js.
My feeling is that the general structure of my routes is incorrect, but I'm not sure. I believe I followed some official React Router v6 sample code.

Related

What is the difference between <Route /> vs <Route> </Route> in React?

I am using the react-router-dom and creating some routes in my application. Can anyone please explain me the specific usages of and . What will be the difference affect in the rendering if there are any. I will include a sample code snippet.
import { BrowserRouter as Router, Route } from 'react-router-dom';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Router>
<Route path="/home" component={App} />
<Route path='/about'>
<About />
</Route>
</Router>
);
I tried both and both are working fine. But I was unable to find the specific usage of one over other.
The react-router-dom#5 Route component has 4 ways to render content. The primary method is to directly render content as the children prop. Note here that no route props will be passed to the component.
<Route path='/about'>
<About />
</Route>
The other 3 ways are to use one of the route render methods.
The recommended method of rendering something with a <Route> is to use
children elements, as shown above. There are, however, a few other
methods you can use to render something with a <Route>. These are
provided mostly for supporting apps that were built with earlier
versions of the router before hooks were introduced.
<Route component>
<Route render>
<Route children> function
Examples:
component - Other common method, route props are implicitly passed as props to the component.
<Route path='/about' component={About} />
render function - Alternative to pass along runtime props as well as the route props.
<Route path='/about' render={routeProps => <About {...routeProps} {...otherProps} />} />
children function - Renders regardless of route match and passes route props to component to handle any conditional logic.
<Route path='/about' children={routeProps => <About {...routeProps} />} />
Just a small add to Mr. Drew Reese's answer, the way rendering a component directly inside <Route> allow you to freely pass your own props inside your component (it's more familiar to the way we usually do with normal React components).
Ex: You have a page About, inside, it includes 3 tabs: "About me", "About my team", "About my blog".
const About = () => {
// call APIs, handle...
return (
<>
<Switch>
<Route path='/about-me'>
<Me data={dataMe} />
</Route>
<Route path='/about-team'>
<Team data={dataTeam} />
</Route>
<Route path='/about-blog'>
<Blog data={dataBlog} />
</Route>
</Switch>
</>
)
}

Browserrouting only running when displaying App

I build a website in react with Browserrouting and have the following code & issue
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<BrowserRouter>
<Routes>
<Route path="/" element={<App/>}>
<Route path="/home" element={<Homepage />} />
The Problem I have is when i run the App it opens the "App" and not the actual Homepage.
I tried to fix it by changing change the App to Homepage but it is not working.
In my understanding / needs to be there so the App displays in any other Path and is functioning.
Can some explain me, how i can make the path / still the Homepage without having to type /home to get there?
Thank you !
It appears you are rendering the Homepage in a nested route that is rendering App. In this configuration <Route path="/" element={<App />}> is what is called a Layout Route. App will be rendered when the path is exactly "/" and should render an Outlet component for the nested routes to render their content to.
An issue I see here, and you should have been seeing a react-router-dom invariant error regarding nesting an absolute path within a route rendering on an absolute path. In other words, "/home" is an absolute path and can't be nested under the absolute path "/"; it's not reachable.
If App really is a layout route and you always want it to render then you could remove the path prop from the parent route rendering App, and move Homepage to "/".
<BrowserRouter>
<Routes>
<Route element={<App />}>
<Route path="/" element={<Homepage />} />
</Route>
</Routes>
</BowserRouter>
or convert Homepage into an index route and leave the "/" path on the parent layout route so the relative routing can still work properly and Homepage is rendered on "/".
<BrowserRouter>
<Routes>
<Route path="/" element={<App />}>
<Route index element={<Homepage />} />
</Route>
</Routes>
</BowserRouter>
I should point out that this is a slightly abnormal configuration. Typically the App component would render the Routes and Route components instead of an Outlet and it's assumed to all render on "/" by default. The Homepage component would then be rendered on path="/".
Example:
<BrowserRouter>
<App />
</BowserRouter>
...
const App = () => {
.... app logic ....
return (
<Routes>
<Route path="/" element={<Homepage />} />
</Routes>
);
};
There is OFC, the chance here that you didn't intend to nest these routes and want them to be matched and rendered independently. In this case the solution is to just unnest the Homepage route and continue using absolute paths for the root routes.
Example:
<BrowserRouter>
<Routes>
<Route path="/" element={<App />} />
<Route path="/home" element={<Homepage />} />
</Routes>
</BowserRouter>
I found the issue
I had to
<Route path="" element={<App/>}>
<Route path="/" element={<Homepage />} />
Delete the Path / in App and only add Path / in Homepage.
It works now

React Router: Routes within routes

I am building a React app that has a static marketing site and a dynamic app. I am using a combination of React Router and hooks to separate the two and ensure proper routing throughout.
To begin with, I want users who are logged in to be taken directly to the app when they hit the root ("/") and to the static/marketing site when not logged in. The main marketing site home page has a nav bar that allows users to access other routes such as "/about", whereas the app has a separate nav bar for app navigation.
The problem is, while the authentication based routing for the root route seems to be working, and I can navigate to other routes specified in my top-level file, the routes that are included within my static/marketing site are not accessible.
Top-level/index.js
const routing = (
<Provider store={store}>
<Router>
<NavWrapper />
{/* <Switch> */}
<Route exact path="/" component={AuthWrapper} />
<Route path="/signup" component={SignUp} />
<Route path="/login" component={Login} />
{/* </Switch> */}
</Router>
</Provider>
)
ReactDOM.render(routing, document.getElementById('root'));
AuthWrapper.js (here useAuth() is a React hook that evaluates global Redux isAuthenticated state)
const AuthWrapper = (props) => {
return useAuth() ? <App /> : <Website />
}
export default AuthWrapper;
website/index.js
export const Website = () => {
return (
<>
{/* <SiteNav /> */}
<Switch>
<Route exact path="/" component={LandingPage} />
<Route path="/about" component={AboutPage} />
<Route path="/how-it-works" component={HowItWorksPage} />
<Route path="/plans" component={PlansPage} />
<Route path="/press" component={PressPage} />
<Route path="/faq" component={FAQPage} />
<Route path="/legal" component={LegalPage} />
<Route path="/for-dieticians" component={DietitiansPage} />
<Route path="/for-trainers" component={PTsPage} />
</Switch>
</>
)
};
In my app, when I enter localhost:3000/ (unauthenticated) I am correctly taken to my home/landing page. However, if I try to use the navigation (or type in any subroutes) to access "/about" for example, I get a blank screen (other than the nav bar). In the React dev tools, the component isn't even rendering.
I think this is the issue:
<Route exact path="/" component={AuthWrapper} />
You want Authwrapper to be shown for about as well, right? But the above only matches "/" exactly.
You should enable the switch statement, but move the AuthWrapper Route to the bottom and then let it have this path: "/:rest*" (maybe "/*" works too, haven't used react-router in a while).
That way, it will use the Route if the first 2 don't match.

What's the proper way to redirect with React routing when a user is not logged into the application?

In a React SPA, I have a collection of "pages" under the /src/pages/ folder.
The entry point page is an index.js file under the /src/ folder, where I define a router const like this:
const routing = (
<Router>
<div>
<Switch>
<Route path="/signIn" component={SignIn} />
<Route exact path="/" component={Homepage} />
<Route path="/page1" component={Page1} />
<Route path="/page2" component={Page2} />
<Route path="/page3" component={Page3} />
<Route component={NotFound} />
</Switch>
</div>
</Router>
It works great and all. All pages are navigable like "https://mysuperapp.com/page2" and it will render the Page2 React component.
Concerns arise when I incorporate user session management (log in, log out). If a user is not logged in the app, all pages should automatically redirect to the /signIn page. And viceversa, if a user is already logged, if the /signIn page is accessed, it should automatically redirect to the root homepage.
Right now I have implemented this by adding the following code to all the pages, right after the render() method is declared in the component, like this:
class Page2 extends React.Component {
render() {
if (UserProfile.getUserSessionStatus() !== "logged") {
this.props.history.push("/signIn");
}
}
return (
JSX code to be rendered here...
...
This works, but it feels like a cheap workaround used by someone who is just learning React, not by a professional.
For a proof of concept it works, but I would never dare to use such a thing in a production environment.
So, what's the right, best-practices-aligned way to accomplish this?
One possible approach is to create a Higher Order component(HOC) and use it for protecting any routes that require login.
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={(props) => (
UserProfile.getUserSessionStatus() === "logged"
? <Component {...props} />
: <Redirect to='/login' />
)} />
)
And then use like this
.....
<PrivateRoute path='/page1' component={Page1} />
.......
Hope this helps!

using same component for different route path in react-router v4

I am trying to have separate routes but same component for add/edit forms in my react app like the below:
<Switch>
<Route exact path="/dashboard" component={Dashboard}></Route>
<Route exact path="/clients" component={Clients}></Route>
<Route exact path="/add-client" component={manageClient}></Route>
<Route exact path="/edit-client" component={manageClient}></Route>
<Route component={ NotFound } />
</Switch>
Now in the manageClient component, I parse the query params (I pass in a query string with client id in edit route), I render conditionally based on the query param passed.
The problem is that this doesn't remount the whole component again. Say an edit page is opened, and the user clicks on add component, the URL changes, but the component doesn't reload and hence remains on the edit page.
Is there a way to handle this?
Using different key for each route should force components to rebuild:
<Route
key="add-client"
exact path="/add-client"
component={manageClient}
/>
<Route
key="edit-client"
exact path="/edit-client"
component={manageClient}
/>
One solution is use inline function with component, that will render a new component each time, But this is not a good idea.
Like this:
<Route exact path="/add-client" component={props => <ManageClient {...props} />}></Route>
<Route exact path="/edit-client" component={props => <ManageClient {...props} />}></Route>
Better solution would be, use componentWillReceiveProps lifecycle method in ManageClient component. Idea is whenever we render same component for two routes and switching between them, then react will not unmount-mount component, it will basically update the component only. So if you are making any api call or require some data do all in this method on route change.
To check, use this code and see it will get called on route change.
componentWillReceiveProps(nextProps){
console.log('route chnaged')
}
Note: Put the condition and make the api call only when route changes.
<Route exact path={["/add-client", "/edit-client"]}>
<manageClient />
</Route>
Reference
Version 5.2.0
https://reacttraining.com/react-router/web/api/Route/path-string-string
My problem was we used an common path in-between, which causes dynamic path to not working
<Switch>
<Route key="Home" path="/home" component={Home} />
<Route key="PolicyPlan-create" path="/PolicyPlan/create" component={PolicyPlanCreatePage} />
{/* <Route key="PolicyPlan-list" path="/PolicyPlan" component={PolicyPlanListPage} /> */}
<Route key="PolicyPlan-list" path="/PolicyPlan/list" component={PolicyPlanListPage} />
<Route key="PolicyPlan-edit" path="/PolicyPlan/edit/:id" component={PolicyPlanCreatePage} />
<Route key="cardDesign" path="/cardDesign" component={cardDesign} />
<Route key="Admin-create" path="/admin/create" component={RegisterPage} />
</Switch>
So don't use the path like the commented one, now the code is working
.................
this.props.history.push("/PolicyPlan/edit/" + row.PolicyPlanId);
.............
You can simply provide an array of paths in a single route tag as follows -
<Route exact path={["/add-client", "/edit-client"]} component={manageClient}></Route>

Categories