Is there a way to nest routes in React Router v4?
This works:
<Router basename='/app'>
<main>
<Route path='/' component={AppBar} />
<Route path='/customers' component={Customers} />
</main>
</Router>
This does not:
<Router basename='/app'>
<Route path='/' component={AppBar}>
<Route path='/customers' component={Customers} />
</Route>
</Router>
Customers Component:
import React, { Component, PropTypes } from 'react'
import styled from 'styled-components'
export default class Customers extends Component {
render () {
return (
<Container>
<h1>Customers</h1>
</Container>
)
}
}
const Container = styled.section`
height: 100%;
padding: 15px;
overflow: auto;
`
Best pattern I have found so far.
// main app
<div>
// not setting a path prop, makes this always render
<Route component={AppShell}/>
<Switch>
<Route exact path="/" component={Login}/>
<Route path="/dashboard" component={AsyncDashboard(userAgent)}/>
<Route component={NoMatch}/>
</Switch>
</div>
I can just keep nesting this inside a component and everything works nice including hmr(If using webpack, dont forget to set output.publicPath to "/")
// dashboard component
<div>
// the same way as before, not setting a path prop
// makes it render on every /dashboard/** request
<Route component={DashboardTAB}/>
<Switch>
// longer path (with same root) than others first
<Route path="/dashboard/graphs/longerpath" component={GraphForm}/>
<Route path="/dashboard/graphs" component={Graphs}/>
<Route path="/dashboard/workers" component={List}/>
<Route path="/dashboard/insert" component={InsertComponent}/>
</Switch>
</div>
I adapted this from the docs, seem to work so far. Probably missing something obvious, and yes it is not the v4 way but we need all the routes defined in one place.
function RouteNest(props){ return (
<Route exact={props.exact} path={props.path} render={ p => <props.component {...p} children={props.children}/> } />
)}
export const MainRoutes = props =>
<div className='content layout'>
<Route exact path="/" component={Landing}/>
<Route path={'/contact'} component={Contact}/>
<RouteNest path={'/thing'} component={CompoWithSub}>
<RouteNest path={'/thing/suba'} component={SubComponentA}/>
<RouteNest path={'/thing/subb'} component={SubComponentB}/>
</RouteNest>
</div>
export const CompoWithSub = props => <div>{props.children)</div>
You're AppBar component is in charge of rendering Customers. For customers to be called, you have to render the children of AppBar. Anything directly nested under AppBar is a child of AppBar.
import React from 'react';
const AppBar = ({ children }) => (
<div>
<header>
<h1> stuff </h1>
</header>
{children}
</div>
);
export default AppBar
Please note that only AppBar will render when you visit "/". AppBar and Customers will render when you visit "/customers".
If someone wants to have nested routes without typing prefix of wrapper route I've created something like this in TSX:
Imports:
import * as React from 'react';
import { Route, RouteComponentProps, RouteProps, Switch } from 'react-router-dom';
import Index from 'views/index';
import Login from 'views/login';
import NoMatch from 'views/no-match';
Interfaces:
interface INestedRoutes {
nested?: string;
}
interface INestedRoute extends RouteProps, INestedRoutes {}
NestedRoute and NestedRoutes wrapper:
class NestedRoutes extends React.Component<INestedRoutes> {
public render() {
const childrenWithProps = React.Children.map(this.props.children, (child) => {
return React.cloneElement(
child as React.ReactElement<any>, { nested: this.props.nested },
);
})
return childrenWithProps;
}
}
const NestedRoute: React.SFC<INestedRoute> = (props: INestedRoute) => {
return <Route path={`${props.nested}${props.path}`} component={props.component} />;
};
And routes with wrapper:
const MultiLanguage: React.SFC<RouteComponentProps<any>> = (props: RouteComponentProps<any>) => {
return (
<NestedRoutes nested={props.match.path} >
<NestedRoute path="/test" component={Login} />
<NestedRoute path="/no-match" component={NoMatch} />
</NestedRoutes>
);
};
export default (
<Switch>
<Route path="/:language" component={MultiLanguage}/>
<Route exact={true} path="/" component={Index} />
<Route path="/login" component={Login} />
<Route component={NoMatch} />
</Switch>
);
For nested routes there is a very simple way which i using.
Example main router is be like that
<Router history={history}>
<Switch >
<Route path="/" component={Home}></Route>
</Switch>
</Router>
Inside Home component using Nested Routing be like:
<div className="App">
<Navbar title="Home" links = { NavbarLinks }/>
{this.renderContentPage()}
</div>
renderContentPage will check the URL and render the nested route.
<Route exact path="/" component={Page1}></Route>
<Route exact path="/page1" component={Page1}></Route>
<Route exact path='/page2' component={Page2} />
So inside Home component page1 and page2 components rendered.
Route expects a single children i.e. a component.
It should not be a new Route.
What you can do is to include your nested routes inside your customers component.
Also make sure to remove exact inside the routes in customers component.
Related
Why is rendering the parent component and the child trying to enter the child component
"react-router-dom": "^6.0.1",
when I enter on the route:
http://localhost:3000/dashboard- the view work
http://localhost:3000/dashboard/employee - rendering dashboard and employee view (both views)
http://localhost:3000/dashboard/accounting - rendering dashboard and accounting view (both views)
Documentation:
https://reactrouter.com/docs/en/v6/getting-started/tutorial#nested-routes
index.js
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import App from "./App";
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById("root")
);
App.js
import AppRouter from "./routers/AppRouter";
function App() {
return (
<>
<AppRouter />
</>
);
}
export default App;
AppRouter.js
import { Route, Routes } from "react-router-dom";
import Navbar from "../components/template/Navbar";
import AccountingHomeView from "../components/views/accounting/AccountingHomeView";
import DashboardHomeView from "../components/views/dashboard/DashboardHomeView";
import EmployeeHomeView from "../components/views/employee/EmployeeHomeView";
import HomeView from "../components/views/public/HomeView";
import LoginView from "../components/views/public/LoginView";
const AppRouter = () => {
return (
<div>
<Navbar />
<Routes>
<Route path="/" element={<HomeView />} />
<Route path="dashboard" element={<DashboardHomeView />}>
<Route path="employee" element={<EmployeeHomeView />} />
<Route path="accounting" element={<AccountingHomeView />} />
</Route>
<Route path="/login" element={<LoginView />} />
</Routes>
</div>
);
};
export default AppRouter;
DashboardHomeView.js (with outlet)
import { Outlet } from "react-router-dom";
const DashboardHomeView = function () {
return (
<>
<h1>DashboardHomeView</h1>
<Outlet />
</>
);
};
export default DashboardHomeView;
component children Accounting
import React from "react";
const AccountingHomeView = function () {
return (
<div>
<h1> Accountin</h1>
</div>
);
};
export default AccountingHomeView;
I also initially found this a bit confusing, but with nested routes the "parent" route is considered more of a "layout" component in that it is always rendered when its path matches, and renders all its children routes into its outlet.
const AppRouter = () => {
return (
<div>
<Navbar />
<Routes>
<Route path="/" element={<HomeView />} />
<Route
path="dashboard"
element={<DashboardHomeView />} // <-- always matched/rendered at "/dashboard*"
>
<Route
path="employee"
element={<EmployeeHomeView />} // <-- conditionally matched/rendered
/>
<Route
path="accounting"
element={<AccountingHomeView />} // <-- conditionally matched/rendered
/>
</Route>
<Route path="/login" element={<LoginView />} />
</Routes>
</div>
);
};
const DashboardHomeView = function () {
return (
<>
<h1>DashboardHomeView</h1> // <-- always matched/rendered at "/dashboard*"
<Outlet /> // <-- conditionally matched/rendered children
</>
);
};
Nested-Routes
You may have noticed when clicking the links that the layout in App
disappears. Repeating shared layouts is a pain in the neck. We've
learned that most UI is a series of nested layouts that almost always
map to segments of the URL so this idea is baked right in to React
Router.
I believe what you are expecting is what is called an Index Route. It is what would be rendered on a "/dashboard" route when it isn't a layout/wrapper container.
Notice it has the index prop instead of a path. That's because the
index route shares the path of the parent. That's the whole point--it
doesn't have a path.
Maybe you're still scratching your head. There are a few ways we try
to answer the question "what is an index route?". Hopefully one of
these sticks for you:
Index routes render in the parent routes outlet at the parent route's path.
Index routes match when a parent route matches but none of the other children match.
Index routes are the default child route for a parent route.
Index routes render when the user hasn't clicked one of the items in a navigation list yet.
const AppRouter = () => {
return (
<div>
<Navbar />
<Routes>
<Route path="/" element={<HomeView />} />
<Route path="dashboard" element={<DashboardLayout />}>
<Route path="employee" element={<EmployeeHomeView />} />
<Route path="accounting" element={<AccountingHomeView />} />
<Route index element={<DashboardHomeView />} />
</Route>
<Route path="/login" element={<LoginView />} />
</Routes>
</div>
);
};
const DashboardLayout = function () {
return (
<div /* with any layout styling */>
.... other common layout content
<Outlet />
.... more possible common page content
</div>
);
};
const DashboardHomeView = function () {
return (
<>
<h1>DashboardHomeView</h1>
.... dashboard specific content
</>
);
};
How about using the exact prop for the parent Route. Like <Route exact path="dashboard" element={<DashboardHomeView />}>. This may solve the issue.
I currently have all the Routes in my app defined in App.js. Would like to be able to pass state (as props) from the Alignment component down to the GPfSOA component.
function App() {
return (
<Router>
<div className="App">
<Nav />
<Switch>
<Route path="/" exact component={Home} />
<Route path="/about" exact component={About} />
<Route path="/alignments" exact component={Alignments} />
<Route path="/alignments/:id" exact component={Alignment} />
<Route path="/alignments/segmentinfo/:id" exact component={Segments} />
<Route path="/alignments/segmentinfo/:id/:segid" exact component={Segment} />
<Route path="/alignments/getpoint/:id" exact component={GPfSOA} />
<Route path="/alignments/getstatoff/:id" exact component={GSOfPA} />
<Route path="/alignments/getalsfromxy/:x/:y" exact component={AlignList} />
<Route path="/alignments/getsegsfromxy/:x/:y" exact component={SegmentList} />
<Route path="/alignments/post/create" exact component={AddAlignment} />
<Route path="/alignments/put/update/:id" exact component={EditAlignment} />
<Route path="/alignments/ptso/list" exact component={TogglePoints} />
<Route path="/alignments/ptso/list/:ptid" exact component={Point} />
<Route path="/" render={() => <div>404</div>} />
</Switch>
</div>
</Router>
);
}
The order from parent on down to the greatest grandchild would be App > Alignments > Alignment > GPfSOA. Trying to pass item.alignment (the alignment's name) from the Alignment component down (or over) to the GPfSOA component so that it can be rendered there. item.alignment is a property of the Alignmnet component's state.
Do I need to set these up as nested routes in order to accomplish this (a.k.a. cut and paste all the Routes from App.js that are children of the Alignment component and paste them into the Alignment component)?
Having a hard time understanding how to define a particular component as being a parent and another component as being a child of that component. All the examples I see assume you want to pass props from App.js down to some other component. Looking for examples with React Hooks and React Router in play (functions rather than classes) where you're passing props from a component 'below' App.js down to another component that's further down in the hierarchy. Hope this makes sense.
Found lots of examples such as this one for 'passing function as a render props in Route component' (supposedly the recommended way to do this)
const PropsPage = () => {
return (
<h3>Props Page</h3>
);
};
const App = () => {
return (
<section className="App">
<Router>
...
<Link to="/404-not-found">404</Link>
<Link to="/props-through-render">Props through render</Link>
<Switch>
...
<Route exact path="/props-through-render" render={(props) => <PropsPage {...props} title={`Props through render`} />} />
<Route component={NoMatchPage} />
</Switch>
</Router>
about with browser reload
</section>
);
};
export default App;
But like I stated before, this example and every other one I've found assume you want to pass props from App.js down to another component.
Your issue can be handle with creating Alignment context
import React, { createContext, useState } from "react";
const AlignmentContext = createContext();
const AlignmentContextProvider = ({ children }) => {
const [num, setNum] = useState(1);
};
return (
<AlignmentContext.Provider value={{ num, setNum }}>
{children}
</AlignmentContext.Provider>
);
};
export { AlignmentContext, AlignmentContextProvider };
now wrap your routes needed to be in same context with AlignmentContextProvider
import { AlignmentContextProvider } from 'pathto/context'
<AlignmentContextProvider>
<Route path="/alignments/:id" exact component={Alignment} />
<Route path="/alignments/segmentinfo/:id" exact component={Segments} />
<Route path="/alignments/segmentinfo/:id/:segid" exact component={Segment} />
<Route path="/alignments/getpoint/:id" exact component={GPfSOA} />
</AlignmentContextProvider>
and use useContext hooks for reach values
import React, { useContext } from "react";
import { AlignmentContext } from 'pathto/context';
const GPfSOA = () => {
const { num, setNum } = useContext(AlignmentContext);
Hello in my directions file I set my struct
header
navbar
and my switch
foote
const AppRouter = () => (
<BrowserRouter>
<Route path="/login" component={AuthPage} exact={true} />
<Route path="/dashboard/addProduct" component={AddProduct} exact={true} />
<div>
<Header/>
<Navigation/>
<Container maxWidth="lg" >
<Switch>
<Route path="/" component={LandingPage} exact={true} />
<Route path="/xd" component={AuthPage} exact={true} />
<Route component={NotFoundPage} />
</Switch>
</Container>
</div>
</BrowserRouter>
);
But I have two routes where I didn't want to show my header
footer
and nav bar
which are the login and addproduct routes
how could i do that?
This is a little bit hacky (just to match your current approach) since I'm assuming you just want to hide those components in only those 2 routes, You can use withRouter in your Header and Navigation components, withRouter will provide to you the location prop and you can have a condition like this:
import React from "react";
import { withRouter } from "react-router-dom";
const Navigation = props => {
const { location } = props;
const { pathname } = location;
if (pathname !== "/login" || pathname !== "/dashboard/addProduct" ) {
return (
// Component logic goes here
);
}
// if we're on those routes we should return null to not render anything
return null;
};
export default withRouter(Navigation);
If you want a more robust long term approach this answer could help:
How to hide navbar in login page in react router
I have some nested routes written in react router v4 and in the Navigation component I have an input box.
On submit I need to call a method on a different route (on Landing component) and pass the value. I can't find any example to call a method in Route.
Any other way/ workaround to use a navigation with data and different routes is welcome.
My routes:
return (
<div>
<Navigation callSearch = {this.callSearch.bind(this)} />
<Switch>
<Route path="/u/:slug" component={AuthorPage}/>
<Route path="/:location/:slug" component={PhotoDetails}/>
<Route path="/" component={Landing} />
</Switch>
</div>
)
In Navigation i call callSearch() :
searchPhotos(e) {
e.preventDefault();
if(this.state.searchTerm) {
this.props.callSearch(this.state.searchTerm);
}
}
I have fixed this issue in my application using withRouter from react-router module.
import { Route } from "react-router-dom";
import { withRouter } from "react-router";
class SideBar extends Component {
callSearch = (searchKeyword) => {
if(searchKeyword) this.props.history.push(`/u/${searchKeyword}`);
};
render() {
return (
<div>
<Navigation callSearch = {this.callSearch} />
<Switch>
<Route path="/u/:slug" component={AuthorPage}/>
<Route path="/:location/:slug" component={PhotoDetails}/>
<Route path="/" component={Landing} />
</Switch>
</div>
)
}
export default withRouter(SideBar);
Hope this will help anyone else !!!
I am attempting to render a component when I enter a url that does not exists. However, the component keeps rendering in all routes. I am using react-router-dom#4.1.1. This are the routes that I set up:
import * as React from "react";
import { Route, RouteComponentProps } from "react-router-dom";
import glamorous from "glamorous";
import ElementList from "./elementlist";
import AddElement from "./addelement";
import NotFound from "./NotFound";
const Styling = glamorous.div({
minHeight: 5,
minWidth: 8
});
const NavRouter = () => (
<Styling>
<Route path="/" exact={true} component={ElementList} />
<Route path="/addelement" component={(props:
RouteComponentProps<{}>) => (
<AddElement onSubmitSuccess={() => props.history.push("/")} />
)} />
<Route path="*" exact={true} component={NotFound}/>
</Styling>
);
export default NavRouter;
This is my NotFound component:
import * as React from "react";
const NotFound = () => (
<h1>The page that you are looking is not there.</h1>
);
export default NotFound;
The issue that I am currently facing is that the message: The page that you are looking is not there. keeps popping up on the / and /addelement route when I changed the URL. I am having a hard time trying to make the message appear only when I go to a route that is not defined. Initially, I tried to switch the routes and make the more "detailed" route at the top like this:
const NavRouter = () => (
<Styling>
<Route path="/addelement" component={(props:
RouteComponentProps<{}>) => (
<AddElement onSubmitSuccess={() => props.history.push("/")} />
)} />
<Route path="/" exact={true} component={ElementList} />
<Route path="*" component={NotFound}/>
</Styling>
);
However, it did not solve the issue. Is there a way to prevent the message from appearing on every route that I go to except for routes that are not defined?
You should use a <Switch> component. Per the documentation:
How is this different than just using a bunch of <Route>s?
<Switch> is unique in that it renders a route exclusively. In contrast, every <Route> that matches the location renders inclusively. Consider this code:
<Route path="/about" component={About}/>
<Route path="/:user" component={User}/>
<Route component={NoMatch}/>
If the URL is /about, then <About>, <User>, and <NoMatch> will all render because they all match the path. This is by design, allowing us to compose <Route>s into our apps in many ways, like sidebars and breadcrumbs, bootstrap tabs, etc.
Occasionally, however, we want to pick only one <Route> to render. If we’re at /about we don’t want to also match /:user (or show our “404” page).
Thus, import it from react-router-dom:
import { Route, RouteComponentProps, Switch } from 'react-router-dom';
Then apply it like so (note there is no need for path="*"):
<Switch>
<Route path="/" exact={true} component={ElementList} />
<Route path="/addelement" component={(props:
RouteComponentProps<{}>) => (
<AddElement onSubmitSuccess={() => props.history.push("/")} />
)} />
<Route component={NotFound}/>
</Switch>