Nested routes and Switch - how to pass props.match.params.id? - javascript

I have been trying to understand nested routes and switch in the React v4 Router.
Consider the main router looks like this (simplified):
<Switch>
<Route path="/" component={LoginPage} exact={true} />
<Route path="/dashboard/edit/:id" component={DashboardPage} />
<Route path="/dashboard" component={DashboardPage} />
</Switch>
The "dashboard" component renders the sub-route:
render(){
return (
<div className="note">
<Route to='/edit/:id' render={(props) =>
<div>
<NoteList {...props} />
<EditNotePage {...props} />
</div>
} />
</div>
)
}
The "EditNotePage" component can access the param by:
const mapStateToProps = (state, props) => ({
note: state.notes.find((note) => note.id === props.match.params.id
});
Is this the correct approach?
It seems a little redundant to specify "/dashboard/edit/:id" twice ( ? )
Once in main router and the again in the dashboard component.
However, if I do not match the route in the main router "Switch" the "props.match.params.id" is not accessible since "props.match" will only point to "/dashboard" .
Have I missed something crucial regarding how the React v4 Router works? :)
Kind regards
Kermit

Nope, didn't miss anything. That's how react router v4 works. You define full routes. The trick you can use is that you can grab the current path and prepend it to your "nested path".

Related

How to put <Route children={({match})} => ... > in Jsx fragment. React router dom v6

return (
<>
Some jsx..
<Route
path={modal.info ? `/fullInfo/${this.state.modal.id}`:`/preview/${this.state.modal.id}`}
element={({match}) => {
return (
<ModalWindow
modalVisible={Boolean(match)}
onCloseWindow={this.onCloseWindow}
modalContent={modal}
/>
)
}}
/>
</>
)
If I do that I get an error like: Route tag must be wrapped by Routes tag. I did this feature in a old version of react-router-dom but when I try to do it in the new one there is err..
In your App.js file, wrap the entire app with <BrowserRouter>
const AppWithRouter = () => <BrowserRouter><App /></BrowserRouter>
export default AppWithRouter
And then wrap all your routes in the new <Routes> tag (replaces switch):
<Routes>
<Route path="..." element={...} />
</Routes>
The Routes component effectively replaced the Switch component from v5, and it's required to wrap any Route components. Additionally, the Route components no longer take component, and render and children prop functions, the routed components must use the element prop that takes a ReactElement, a.k.a. JSX.
Wrap the Route component in a Routes component and since all routes are always exactly matched, just render the ModalWindow with the modalVisible prop set to true.
return (
<>
...Some jsx...
<Routes>
<Route
path={modal.info
? `/fullInfo/${this.state.modal.id}`
:`/preview/${this.state.modal.id}`
}
element={(
<ModalWindow
modalVisible
onCloseWindow={this.onCloseWindow}
modalContent={modal}
/>
)}
/>
</Routes>
</>
)

How to lazy load components to React router dom Route component?

I have this:
<Router>
<Route component={MyLazyLoadedComponent} />
</Router>
I thought about doing:
<Router>
<Route render={props => {
import('path/to/component').then(Module => {
return <Module.default {...props.match.params} />
})
}} />
</Router/>
But this ain't working since <Route /> component from the React router Dom isn't async (route is actually rendered BEFORE the import). How to achieve this code splitting?
The question actually also relevant for Webpack as well as Parcel bundler.
I think this should work:
const AsyncComponent = React.lazy(() => import('path/of/component'));
...
<Route path={`path`} render={props => <AsyncComponent {...props} />} />
...
I hope this helps!

Prioritise nested route over main route

Setup
I have an App component rendering following routes:
<Route path="/items/:id" component={ItemDetail} />
<Route path="/items" component={AllItems} />
In my AllItems component I render a list of all items and the option to create a new item or update an existing one. Doing either one of those actions opens a popup. To do this I render following routes in AllItems:
<Route path="/items/add" component={AddItemModal} />
<Route path="/items/edit" component={EditItemModal} />
Note: It's important that these modals are actually linked to these routes, I can't change that. Neither can I render those routes outside of AllItems as I need to pass soms props to the modals.
Problem
When I go to a route like /items/1: ItemDetail renders (as expected).
When I go to /items/add: ItemDetail renders with add as :id.
I need it to render AddItemModal here as defined in AllItems.
What I tried:
I tried adding exact to the /items/:id route and I also tried adding it to /items/add & /items/edit. Neither of those solutions worked. Either only ItemDetail rendered, or only the modals.
I tried defining /items before /items/:id to hopefully give higher priority to the nested routes. ItemDetail never rendered in this case.
Is there a solution to this so I can prioritise items/add & items/edit over items/:id
Try nesting the routes under /items
<Route
path="/items"
render={() => (
<>
<Route path="" component={AllItems} exact />
<Route path="/add" component={AddItemModal} />
<Route path="/edit" component={EditItemModal} />
<Route path="/:id" component={ItemDetail} />
</>
)}
/>
If you want to have an independent views for ItemDetail and AllItems but at the same time have /items/add and /items/:id/edit (took a little liberty with the url, you need and id to edit an item right?) as modals over AllItems so the structure of the routes would be something like this:
AllItemsView (/items)
AddItemModal (/items/new)
EditItemModal (/items/:id/edit)
ItemDetailView (/items/:id)
You need a little tweak of Tnc Andrei response:
<Route
path="/items"
render={({ match: {url, isExact}, location: {pathname} }) => {
let pathnameArray = pathname.split("/")
let lastChunk = pathnameArray[pathnameArray.length - 1]
if (isExact || lastChunk === "new" || lastChunk === "edit") {
return (
<>
<Route path={`${url}/`} component={CompetitionsView} />
<Switch>
<Route path={`${url}/new`} component={CompetitionFormModal} />
<Route path={`${url}/:competitionId/edit`} component={CompetitionFormModal} />
</Switch>
</>
)
}
return (
<>
<Route path={`${url}/:competitionId`} component={CompetitionView} />
</>
)
}}
/>

Don't render component on specific route

I have a React-based web application that utilizes React Router to map pages to different URLs:
export const Container = () => (
<div>
<SideNav/>
<div>
<Switch>
<Route path="/login" component={LoginView} />
<Route path="/route1" component={RouteOne} />
<Route path="/route2" component={RouteTwo} />
</Switch>
</div>
</div>
)
When any route gets hit, the sidebar gets rendered as well as the appropriate view. However, I am trying to build the layout such that for certain routes (such as "login"), the SideNav doesn't get rendered and the component (in this case, LoginView) is the only thing that gets rendered. In other words, LoginView should take over the div and be the only child of the top div.
Is there anyway this can be done?
According to react-router docs:
path: string Any valid URL path that path-to-regexp understands.
path-to-regexp understand a string, array of strings, or a regular expression.
Array of routes:
State which routes will render the SideNav as well (Working Example):
<Route path={['/route1', '/route2']} component={SideNav} />
RegExp:
Another option is to show the SideNav only if the path doesn't contain a certain word (working example):
<Route path={/^(?!.*login).*$/} component={SideNav} />
And in your code:
export const Container = () => (
<div>
<Route path={['/route1', '/route2']} component={SideNav} />
<div>
<Switch>
<Route path="/login" component={LoginView} />
<Route path="/route1" component={RouteOne} />
<Route path="/route2" component={RouteTwo} />
</Switch>
</div>
</div>
)
Another approach (I am not sure about this but I faced the same problem and this is how I fixed it, but I admit it's less cleaner than what Ori Drori proposed):
In your SideNav component :
import React from "react";
import {useLocation} from "react-router"
export const SideNav = (props) => {
const location = useLocation();
const show = !location.pathname.includes("login");
return (
<>
{show && (<YourLoginComponentCode /> }
</>
)
}

Passing props to component in React Router 4

I am new to react-router and I just started writing an app using react-router V4. I would like to to pass props to components rendered by <Match /> and I am wondering about what's the 'best' or 'proper' way to do so.
Is it by doing something like this?
<Match pattern="/" render={
(defaultProps) => <MyComponent myProp = {myProp} {...defaultProps} />
}/>
Is this (passing props to components to be rendered by <Match />) even a good practice to do so with react-router or is it an antipattern or something; and if so, why?
You must use the render prop instead of component to pass on custom props, otherwise only default Route props are passed ({match, location, history}).
I pass my props to the Router and child components like so.
class App extends Component {
render() {
const {another} = this.props
return <Routes myVariable={2} myBool another={another}/>
}
}
const Routes = (props) =>
<Switch>
<Route path="/public" render={ (routeProps) =>
<Public routeProps={routeProps} {...props}/>
}/>
<Route path="/login" component={Login}/>
<PrivateRoute path="/" render={ (routeProps) =>
...
}/>
</Switch>
render() {
return (
<Router history={browserHistory}>
<Switch>
<Route path="/"
render={ () => <Header
title={"I am Title"}
status={"Here is my status"}
/> }
/>
<Route path="/audience" component={Audience}/>
<Route path="/speaker" component={Speaker}/>
</Switch>
</Router>
)
}
I'm fairly new to react-router and came across a similar issue. I've created a wrapper based on the Documentation and that seems to work.
// Wrap Component Routes
function RouteWrapper(props) {
const {component: Component, ...opts } = props
return (
<Route {...opts} render={props => (
<Component {...props} {...opts}/>
)}/>
)
}
<RouteWrapper path='/' exact loggedIn anotherValue='blah' component={MyComponent} />
So far so good
I use render in combination with a defined method like so:
class App extends React.Component {
childRoute (ChildComponent, match) {
return <ChildComponent {...this.props} {...match} />
}
render () {
<Match pattern='/' render={this.childRoute.bind(this, MyComponent)} />
}
}
The render prop is meant for writing inline matches, so your example is the ideal way to pass extra props.
I'll do it like the following to improve clarity.
const myComponent = <MyComponent myProp={myProp} {...defaultProps} />
<Match pattern="/" component={myComponent} />
By this your router code won't get messed up!.

Categories