I have nested routes, the third level routes are failing.
This is my routing structure
yeah.... but the problem is,
App.js Routes to
-Home
-About
-Dashboard
Then Dashboard has children component to
-Profile (/dashboard/user)
-Account (/dashboard/account)
-Wallet (/dashboard/wallet)
-Settings (/dashboard/settings)
Then Settings has other children components
-Edit Profile (/dashboard/settings/editprofile)
-Edit Password and Pin (/dashboard/settings/editpassword)
-Edit Account Number (/dashboard/settings/editnumber)
The top two level routes are working, but the last one fails when i refresh the page, although it renders when i go back to the homepage and click on the links till i get to the last component, but once i refresh my browser it breaks, also it doesn't work when i type it manually.
Here is my App.js Route setup
import {
BrowserRouter as Router,
Switch,
Route,
Redirect,
} from "react-router-dom";
const App = () => {
return (
<div className="App">
{/* setting up the routes */}
<Router>
<Switch>
<Route path="/" exact component={Home} />
<Route path="/dashboard" component={dashboard} />
<Route path="/login" exact component={Login} />
<Route path="/register" exact component={SignUp} />
<Route path="/about" exact component={AboutServices} />
</Switch>
</Router>
</div>
);
};
My DashBoard.js
import { Route, useRouteMatch, NavLink, Switch } from "react-router-dom";
const App = () => {
let { path, url } = useRouteMatch();
return (
<div className="App">
<nav> Navavigation bar <nav>
{/* setting up the routes */}
<div className="MainBody">
<Switch>
<Route path={`${path}/wallet`} exact component={Wallet} />
<Route path={`${path}/profile`} component={Profile} />
<Route path={`${path}/account`} component={Account} />
<Route path={`${path}/settings`} exact component={Settings} />
</Switch>
</div>
</div>
);
};
Settings Page
import {
Switch,
Route,
useRouteMatch,
NavLink,
BrowserRouter,
} from "react-router-dom";
const Settings = (props) => {
let { path, url } = useRouteMatch();
return (
<div className="Settings">
<BrowserRouter>
<nav> Navavigation bar <nav>
<div className="SettingsWrapper">
<Switch>
<Route path={`${path}/editprofile`} component={EditProfile} />
<Route
path={`${path}/changepassword`}
component={ChangePassword}
/>
<Route path={`${path}/changepin`} component={ChangePin} />
<Route
path={`${path}/accountsettings`}
component={BankSettings}
/>
</Switch>
</div>
</div>
</BrowserRouter>
</div>
);
};
export default Settings;
I am 99% sure your issue is because you are using more than 1 router. Remove the BrowserRouter around your Settings UI. I am guessing when the nested routes aren't navigated to via links from the outer router that the match prop isn't initialized as you expect it to be.
const Settings = (props) => {
let { path, url } = useRouteMatch();
return (
<div className="Settings">
<nav>Navigation bar</nav>
<div className="SettingsWrapper">
<Switch>
<Route path={`${path}/editprofile`} component={EditProfile} />
<Route
path={`${path}/changepassword`}
component={ChangePassword}
/>
<Route path={`${path}/changepin`} component={ChangePin} />
<Route
path={`${path}/accountsettings`}
component={BankSettings}
/>
</Switch>
</div>
</div>
);
};
Edit
Ok, when removing the nested router and creating my own codesandbox I found a small, subtle but important "quirk" of nesting routes. Any nested route that is rendering further routes can not specify the exact prop on the route.
const App = () => {
let { path, url } = useRouteMatch();
return (
<div className="App">
<nav>Navigation bar</nav>
{/* setting up the routes */}
<div className="MainBody">
<Switch>
...
<Route
path={`${path}/settings`}
exact // <-- exact match precludes sub-routes!!
component={Settings}
/>
</Switch>
</div>
</div>
);
};
So if for example the path was "/dashboard/settings/editprofile" the path no longer exactly matches the route path and the route is not rendered.
Solution
Simply omit the exact prop for nested routes rendering sub-routes. Remember that route paths are to be considered "prefixes", so without the exact prop specified that path "/dashboard/settings/editprofile" can be matched by "/dashboard/settings".
const Dashboard = () => {
let { path, url } = useRouteMatch();
return (
<div className="App">
<nav>Dashboard Navigation bar </nav>
<NavLink to={`${url}/settings`}>Settings</NavLink>
{/* setting up the routes */}
<div className="MainBody">
<Switch>
...
<Route path={`${path}/settings`} component={Settings} /> // <-- no exact match
</Switch>
</div>
</div>
);
};
Related
Below is my code snippet simplified. (using react-router-v5)
My question is how to get access to BrowserRouter's history in the logout_Handler(), given that I am "outside" BrowserRouter?
I've seen this answer How to access history object outside <Route /> in React Router v4
, but there is also a comment which applies to my case:
"When I try to use "withRouter" as shown here I get the error
You should not use <Route> or withRouter() outside a <Router>
which is exactly what I get, if I try to use withRouter at the App.js level.
// App.js
function App(props) {
const logout_Handler = (e) => {
localStorage.removeItem("app-tokenObj");
// how to get the history from BrowserRouter to redirect?!
//history.push("/login") ?? NO IDEA ??
}
return (
<BrowserRouter>
<nav>
<h3>My Supa Dupa App</h3>
<Link to="/">Home Page</Link>
<Link to="/admin">Admin Page</Link>
<button type="button" onClick={logout_Handler}>Logout</button>
</nav>
<hr/>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/link1" component={Comp1} />
<Route path="/link2" component={Comp1} />
<Route path="/login" component={Login} />
<Route path="/signup" component={Signup} />
</Switch>
</BrowserRouter>
)
}
If you are trying to access parts of a routing context in the App component then the router providing the context needs to be higher in the ReactTree. The solution is simple. Move BrowserRouter higher than App in your apps code. Wrapping App in the BrowserRouter would be sufficient enough for this.
Example index.js
ReactDOM.render(
...
<BrowserRouter>
<App />
</BrowserRouter>
...,
root,
);
App
The App component now has a routing context available. While you could use the withRouter Higher Order Component, using the useHistory hook would be simpler.
import { Link, Route, useHistory } from 'react-router-dom';
function App(props) {
const history = useHistory();
const logout_Handler = (e) => {
localStorage.removeItem("app-tokenObj");
history.push("/login");
}
return (
<>
<nav>
<h3>My Supa Dupa App</h3>
<Link to="/">Home Page</Link>
<Link to="/admin">Admin Page</Link>
<button type="button" onClick={logout_Handler}>Logout</button>
</nav>
<hr/>
<Switch>
<Route path="/link1" component={Comp1} />
<Route path="/link2" component={Comp1} />
<Route path="/login" component={Login} />
<Route path="/signup" component={Signup} />
<Route path="/" component={Home} />
</Switch>
</>
);
}
#for move one folder previous:
history.push("/../login")
I am trying to set up a login page for my app, but when I try to redirect using this.props.history.push the new page does not render. My app uses redux which wraps my main file AsyncApp with Provider. AsyncApp has all my routes wrapped with various navigation bars that appear on every page. Now I am trying to do a login page but I don't know how to implement its route in my application since its route does not use the navigation bars therefore it will not reside in AsyncApp. I dont want to rename all my existing pages because the login page is the only page that does use the navigation bars.
I have tried making a component APP that is wrapped my the provider and has a route for the login page and the other routes. This isn't working.
Root.js
const store = configureStore()
export default class Root extends Component {
render() {
return (
<Provider store={store}>
<App />
</Provider>
)
}
}
App.js
export default class App extends Component {
render() {
let arr = window.location.pathname.split('/');
let loc = arr[1];
if(loc === 'signin'){
return (
<Router>
<Route exact path="/signin" component={SignIn} />
</Router>
)
} else {
return (
<AsyncApp />
)
}
}
}
AsyncApp.js
class AsyncApp extends Component {
render() {
const { classes } = this.props
return (
<ThemeProvider theme={theme}>
<div className={classes.root}>
<CssBaseline />
<nav className={classes.drawer}>
<Hidden xsDown implementation="css">
<Navigator PaperProps={{ style: { width: drawerWidth } }} />
</Hidden>
</nav>
<div className={classes.appContent}>
<Header onDrawerToggle={this.handleDrawerToggle} />
<main className={classes.mainContent}>
<div>
<Router>
<Route exact path="/EditContracts/:contractId/sections/:section" component={EditSection} />
<Route exact path="/EditContracts/:contractId" component={EditContract} />
<Route exact path="/EditUsers/:userId" component={EditUser} />
<Route exact path="/EditEndpoints/:epId" component={EditEndpoint} />
<Route exact path="/EditContracts/:contractId/addSection" component={CreateSection} />
<Route exact path="/Contracts/List" component={Contracts} />
<Route exact path="/Contracts/Create" component={CreateContract} />
<Route exact path="/Contracts/Import" component={ImportContract} />
<Route exact path="/Users/List" component={Users} />
<Route exact path="/Users/Create" component={CreateUser} />
<Route exact path="/Endpoints/Create" component={CreateEndpoint} />
<Route exact path="/Endpoints/List" component={Endpoints} />
</Router>
</div>
</main>
</div>
</div>
</ThemeProvider>
)
}
}
I expect to be able to keep AsyncApp how it is while being able to have a login page that can redirect to any page on AsyncApp.
1) Wrap the entire app around a router so you don't have to have multiple routers set up:
export default class Root extends Component {
render() {
return (
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
)
}
}
2) Utilize the Switch function to route your pages
export default class App extends Component {
render() {
<Switch>
<Route exact path="/signin" component={SignIn} />
<Route path="/" component={AsyncApp} />
</Switch>
}
}
class AsyncApp extends Component {
...
<Switch>
<Route exact path="/EditContracts/:contractId/sections/:section" component={EditSection} />
<Route exact path="/EditContracts/:contractId" component={EditContract} />
<Route exact path="/EditUsers/:userId" component={EditUser} />
<Route exact path="/EditEndpoints/:epId" component={EditEndpoint} />
<Route exact path="/EditContracts/:contractId/addSection" component={CreateSection} />
<Route exact path="/Contracts/List" component={Contracts} />
<Route exact path="/Contracts/Create" component={CreateContract} />
<Route exact path="/Contracts/Import" component={ImportContract} />
<Route exact path="/Users/List" component={Users} />
<Route exact path="/Users/Create" component={CreateUser} />
<Route exact path="/Endpoints/Create" component={CreateEndpoint} />
<Route exact path="/Endpoints/List" component={Endpoints} />
</Switch>
...
3) In your SignIn component add a state variable called redirect that you set to true if you are signed in. Then
if (redirect) {
return <Redirect to="path/to/redirect" />
}
This will set up your routes and allow you to do your redirects w/out manipulating the window and refreshing the app
I'm new to react and trying to get this whole routing thing down. I have page which I want to render multiple routes withing.
My main index.js file looks like this:
ReactDOM.render(
<BrowserRouter>
<div>
<Switch>
<Route path="/adminDash" exact component={AdminDashMain}/>
<Route path="/admin/ClientSearch" exact component={ClientDetailsMain}/>
<Route path="/" exact component={LogIn}/>
</Switch>
</div>
</BrowserRouter>
, document.getElementById('root'));
in client search main I have 3 components
class ClientDetailMain extends React.Component {
render() {
return(
<div>
<Header />
<SubHeader username={this.props.match.params.username} />
<Display username={this.props.match.params.username}/>
</div>
);
}
}
export default withRouter(ClientDetailMain);
I'm using <Display/> as a container and inside of that I want to have other route so that a person can go to
/admin/ClientSearch/refined
/admin/ClientSearch/general
/admin/ClientSearch/fixed
I figured out that the /admin/ClientSearch will match regardless so the header and subheader show on all 3 routes, however my routes which are written as:
const Display = () =>{
return(
<div>
<Route path ='/admin/ClientSearch/refined' component={<Refined/>
<Route path ='/admin/ClientSearch/general' component={<General/>
<Route path ='/admin/ClientSearch/fixed' component={<Fixed/>
</div>
)
};
export default withRouter(ClientDisplay);
aren't displaying anything. Is this how I should be writing it? When I link to and of those 3 the header and subheader show up but the components in the individuals routes don't.
For example
'/admin/ClientSearch/fixed' shows the header and subheader but none of its own components.
They key is in the "exact" attribute of your Routes. In addition, when you create a component that has routes inside, you can get the url of the previous routes through it's props. Like this example:
class Main extends React.Component {
render(){
return (
<Switch>
<Route exact path='/' component={Home} />
<Route exact path='/about' component={About} />
<Route exact path='/contact' component={Contact} />
<Route path='/admin' component={AdminArea} />
</Switch>
)
}
}
Then you have your sub-routes like this:
const AdminArea = ({match}) => (
<Switch>
<Route exact path={`${match.url}/specie`} component={Component} />
<Route exact path={`${match.url}/color`} component={Component} />
<Route exact path={`${match.url}/user/:id`} component={Component}/>
</Switch>
)
I want to hide the navbar in a login page.
I did it actually, but I can't see the navbar on other pages.
This code is part of My App.jsx file.
I make history in App's state. And I hide navbar, when this pathname is '/' or '/login'.
It works!
But then I typed the ID and password, and clicked the login button, got 'success' result, and navigated to '/main'.
Now I can't see navbar in main component too.
How can I do this?
Sorry about my short english. If you can't understand my question, you can comment.
constructor(props) {
super(props);
this.state = {
isAlertOpen: false,
history: createBrowserHistory(),
};
this.toggleAlert = this.toggleAlert.bind(this);
}
<BrowserRouter>
<div className="App">
{this.state.history.location.pathname === '/' || this.state.history.location.pathname === '/login' ? null
: <Header toggleAlert={this.toggleAlert} />}
<div className="container">
{this.state.history.location.pathname === '/' || this.state.history.location.pathname === '/login' ? null
: <Navbar />}
<Route exact path="/" render={() => <Redirect to="/login" />} />
<Route path="/login" component={Login} />
<Route path="/main" component={Main} />
<Route path="/user" component={User} />
<Route path="/hw-setting" component={Setting} />
<Route path="/hw-detail/:id" component={HwDetail} />
<Route path="/gas-detail/:id" component={GasDetail} />
{this.state.isAlertOpen ? <Alert /> : null}
</div>
</div>
</BrowserRouter>
login(event) {
event.preventDefault();
userService.login(this.state.id, this.state.password).subscribe(res => {
if (res.result === 'success') {
global.token = res.token;
this.props.history.push('/main');
} else {
alert(`[ERROR CODE : ${res.statusCode}] ${res.msg}`);
}
});
You could structure your Routes differently so that the Login component doesn't have the Header Like
<BrowserRouter>
<Switch>
<div className="App">
<Route exact path="/(login)" component={LoginContainer}/>
<Route component={DefaultContainer}/>
</div>
</Switch>
</BrowserRouter>
const LoginContainer = () => (
<div className="container">
<Route exact path="/" render={() => <Redirect to="/login" />} />
<Route path="/login" component={Login} />
</div>
)
const DefaultContainer = () => (
<div>
<Header toggleAlert={this.toggleAlert} />
<div className="container">
<Navbar />
<Route path="/main" component={Main} />
<Route path="/user" component={User} />
<Route path="/hw-setting" component={Setting} />
<Route path="/hw-detail/:id" component={HwDetail} />
<Route path="/gas-detail/:id" component={GasDetail} />
{this.state.isAlertOpen ? <Alert /> : null}
</div>
</div>
)
As of the latest release of React Router v6, it is no longer possible to pass a <div> component inside the Routes (v6) aka Switch(v5 or lower) to render a Navbar. You will need to do something like this:
Create two Layout components. One simply renders a Nav and the other one does not. Suppose we name them
<WithNav />
<WithoutNav />
You will need to import <Outlet /> from the React router and render inside the Layout components for the routes to be matched.
Then in your App or where ever you have your Router you will render like below ....
// WithNav.js (Stand-alone Functional Component)
import React from 'react';
import NavBar from 'your navbar location';
import { Outlet } from 'react-router';
export default () => {
return (
<>
<NavBar />
<Outlet />
</>
);
};
// WithoutNav.js (Stand-alone Functional Component)
import React from 'react';
import { Outlet } from 'react-router';
export default () => <Outlet />
// your router (Assuming this resides in your App.js)
<Routes>
<Route element={<WithoutNav />}>
<Route path="/login" element={<LoginPage />} />
</Route>
<Route element={<WithNav />}>
<Route path="/=example" element={<Example />} />
</Route>
</Routes>
LoginPage will not have a nav however, Example page will
Simplest way is use div tag and put components in which you want navbar and put login route component outside div tag:
<div className="App">
<Router>
<Switch>
<Route exact path="/" component={Login} />
<div>
<NavBar />
<Route exact path="/addproduct" component={Addproduct}></Route>
<Route exact path="/products" component={Products}></Route>
</div>
</Switch>
</Router>
</div>
Put the Route with path="/" below every other routes :
<Switch>
<Route path="/login" component={Login} />
<Route path="/" component={Home} />
</Switch>
It will work.
I'm was trying to solve this problem, what i did was add component helmet, to install it use : yarn add react-helmet --save.
import {Helmet} from 'react-helmet';
<Helmet>
<script src="https://kit.fontawesome.com/.....js" crossorigin="anonymous"></script>
</Helmet>
The accepted answer has problem if you need to add other default route within the switch if no other route matches, e.g., 404 page, not found page.
I ended up using simple css to hide navigation bar inside my login page.
class LoginPage extends React.Component<>{
...
// Hide navigation bar in login page. Do it inside ComponentDidMount as we need to wait for navbar to render before hiding it.
componentDidMount(){
document.getElementById('navigation-bar')!.style.display = "none";
}
componentWillUnmount(){
document.getElementById('navigation-bar')!.style.display = "flex";
}
render(){
return(
// your login/signup component here
...
)
}
}
I just started learning React, I'm trying to make a SPA blog, which has a global positioned fixed header.
import React from 'react';
import { render } from 'react-dom';
// import other components here
render((
<Router history={browserHistory}>
<Route path="/" component={Home} />
<Route path="/About" component={About} />
<Route path="/Contact" component={Contact} />
<Route path="*" component={Error} />
</Router>
), document.getElementById('app'));
So, each routes has the same header and from my angular background, I would use header outside ui-view.
Its a good practice to import the header component in each individual page component, or can I add the header component on my <Router><myHeader/><otherRoutes/></Router>?
Update:
I was thinking to use something like this:
Routes component, where I define my routes:
class Routes extends React.Component {
render() {
return (
<Router history={browserHistory}>
<IndexRoute component={Home} />
<Route path="/studio" component={Studio} />
<Route path="/work" component={Work} />
<Route path="*" component={Home} />
</Router>
)
}
}
And then on main Index.js file I would like to render something like:
import Routes from './components/Routes';
render((
<div>
<div className="header">header</div>
<Routes />
</div>
), document.getElementById('app'));
Can someone explain me? Thanks!
From my experience it can be good to define a layout component for your page, something like...
Layout Component
render() {
return(
<div>
<Header />
{ this.props.children }
/* anything else you want to appear on every page that uses this layout */
<Footer />
</div>
);
}
You then import layout into each of your page components...
Contact Page Component
render() {
return (
<Layout>
<ContactComponent />
/* put all you want on this page within the layout component */
</Layout>
);
}
And you can leave your routing the same, your route will render the contact page and in turn will render your header.
This way you get control of repetitive stuff that will be on multiple pages, if you need one or two slightly different pages you can just create another layout and use that.
I find this way useful:
import React, { Component } from 'react';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import Header from "./components/Header";
import Home from "./components/Home";
import Dashboard from "./components/Dashboard";
import Footer from "./components/Footer";
class App extends Component {
constructor() {
super();
this.state = {
stuff: stuff;
};
}
render() {
let { stuff } = this.state;
return (
<Router> //wrapper for your router, given alias from BrowserRouter
<div className="App">
<Header /> //this component will always be visible because it is outside of a specific Route
<Route exact path="/" component={Home}/> //at the root path, show this component
<Route path="/dashboard" component={()=><Dashboard stuff={stuff} />}/> //at the path '/dashboard', show this other component
<Footer /> //this is also permanently mounted
</div>
</Router>
);
}
}
export default App;
credit goes to: David Kerr
The question is already answered but I'm here to show another approach and say why I prefer that.
I also think that it's a good thing to have a Layout component
function Layout (props) {
return (
<div>
<Header/>
<div className="content">
{props.children}
</div>
</div>
);
}
But instead of render it into each route component you can render it just once as a parent for your routes.
return (
<Router>
<Layout>
<Switch>
<Route path="/about">
<About/>
</Route>
<Route path="/contact">
<Contact/>
</Route>
<Route path="/">
<Home/>
</Route>
</Switch>
</Layout>
</Router>
);
This is good because in most cases you will not waste time with the layout and if you have different layouts you only need to work inside the Layout component.
For forcefully refresh Header inside routing. use forceRefresh={true}
const Routing = () => {
return(
<BrowserRouter forceRefresh={true}>
<Header/>
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/list/:id" component={ListingApi}/>
<Route path="/details/:id" component={HotelDetails}/>
<Route path="/booking/:hotel_name" component={PlaceBooking}/>
<Route path="/viewBooking" component={ViewBooking}/>
<Route exact path="/login" component={LoginComponent}/>
<Route path="/signup" component={RegisterComponent}/>
</Switch>
<Footer/>
</BrowserRouter>
)
}
I've another solution that is make the routing like this
const Routing = () => {
return(
<BrowserRouter forceRefresh={true}>
<Header/>
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/list/:id" component={ListingApi}/>
<Route path="/details/:id" component={HotelDetails}/>
<Route path="/booking/:hotel_name" component={PlaceBooking}/>
<Route path="/viewBooking" component={ViewBooking}/>
<Route exact path="/login" component={LoginComponent}/>
<Route path="/signup" component={RegisterComponent}/>
</Switch>
<Footer/>
</BrowserRouter>
)
}
and in the header file edit as this
import React, {useLocation} from "react";
const header = () => {
const location = useLocation();
return(
<div className={location.pathname === 'login' ? 'd-none': 'd-block'}>
abc
</div>
)
}
export default header;
this will hide the header panel in login page.