React Router - are nested <Switch> components an anti-pattern? - react-router

From React Router's docs:
All children of a <Switch> should be <Route> or <Redirect> elements. Only the first child to match the current location will be rendered.
Nonetheless, nested <Switch> statements are allowed. I use the pattern to break up large numbers of <Routes>:
<Switch>
<Route path="/foo" component={FooRouter} />
<Route path="/bar" component={BarRouter} />
<Route path="/baz" component={BazRouter} />
</Switch>
...
const FooRouter = () => (
<Switch>
<Route exact path="/foo/:id" component={ViewFoo} />
<Route exact path="/foo/new" component={NewFoo} />
</Switch>
)
const BarRouter = () => (
<Switch>
<Route exact path="/bar/new" component={NewBar} />
</Switch>
)
....
Curious if there is a better method for breaking up large numbers of routes and if nested <Switch> statements should be avoided?

as you solve it just fine when you have a lot of nested route yo can speared them across the app and make a dynamic routes
but soon react-router-dom v6 will be release with a huge upgrade one of them is useRoutes
that let you configure your routes like this:
let element = useRoutes([
// A route object has the same properties as a <Route>
// element. The `children` is just an array of child routes.
{ path: '/', element: <Home /> },
{
path: 'users',
element: <Users />,
children: [
{ path: '/', element: <UsersIndex /> },
{ path: ':id', element: <UserProfile /> },
{ path: 'me', element: <OwnUserProfile /> },
]
}
]);
introduction to react-router-dom v6 they have some cool new feature that worth to watch for
one of them is the replace of with witch help you a lot with nested routes and fun thing you don't gonna need to use the exact anymore
<Routes>
<Route path="/" element={<UsersIndex />} />
<Route path=":id" element={<UserProfile />} />
<Route path="me" element={<OwnUserProfile />} />
</Routes>
this is how it gonna look with the new feature

A note on nested conditional routes: Switch must only have Route children. If you declare Switch inside Switch, every route after Switch won't be used, i.e.
<Switch>
<Route path="/1" />
<Switch> ... </Switch>
<Route path="/2" /> // this one won't work!
</Switch>
So don't do this, stick to declaring one route per condition or render routes as an array under common condition:
<Switch>
{condition && <Route path="/1" >}
{condition && <Route path="/2">}
{/* or */}
{anotherCondition && [
// notice `key`. React will warn you about rendering a list without key prop
<Route key="3" path="/3">,
<Route key="4" path="/4">,
]}
</Switch>
This is true for react-router-dom v5, not sure about 6.

Related

Can we put html inside Route element?

<Router>
<Routes>
<Route path='/' element={<Navbar />} />
<Route path='/' element={<div className='recipes'>
{query ? query.map((object, i) => (
<RecipeCard
src={object.src}
ingredients={object.ingredients}
steps={object.steps}
key={i}
/>
)) : "Loading"}
</div>}/>
<Route path='/' element={<Details />} />
</Routes>
</Router>
For example in the above code, I want to render the HTML along with the Route element.
I am not getting the 2nd and 3rd Route tags displayed on my localhost. Where is my mistake?
What is the correct way to do this?
The only requirement is that the element prop takes a React.ReactNode. In other words, it takes any valid JSX.
The issue though is that there can only be one route per path. Your code is trying to render 3 routes on the same "/" path. Just unconditionally render the Navbar and Details components not on a route.
Example:
<Router>
<Navbar />
<Routes>
<Route
path='/'
element={(
<div className='recipes'>
{query
? query.map((object, i) => (
<RecipeCard
src={object.src}
ingredients={object.ingredients}
steps={object.steps}
key={i}
/>))
: "Loading"
}
</div>
)}
/>
</Routes>
<Details />
</Router>
If you are wanting to conditionally render Navbar and Details on only certain routes then create a layout route component.
Layout Route
Outlet
Example:
import { Outlet } from 'react-router-dom';
const Layout = () => (
<>
<Navbar />
<Outlet /> // <-- nested routes render element content here
<Details />
</>
);
Render the routes you want to have the navbar and details as nested routes, and for the routes you don't want them render these as sibling routes.
<Router>
<Routes>
<Route element={<Layout />}>
<Route
path='/'
element={(
<div className='recipes'>
{query
? query.map((object, i) => (
<RecipeCard
src={object.src}
ingredients={object.ingredients}
steps={object.steps}
key={i}
/>))
: "Loading"
}
</div>
}
/>
...other routes with Navbar and Details...
</Route>
...other routes w/o Navbar and Details...
</Routes>
</Router>

react-router-dom 6 upgrade help: All component children of <Routes> must be a <Route> or <React.Fragment>

Our application recently updated to the beta versions of react-router-dom, and things were fine. Then when I try to update to 6.0.2, I get lots of invariant errors about All component children of <Routes> must be a <Route> or <React.Fragment>. This is because we have our routes defined as follows:
Feature.jsx:
export const FeatureRoutes = () => (
<Routes>
<Route path="/" element={<Feature1 />} />
<Route path="/*" element={<NotFound />} />
</Routes>
);
routes.jsx:
export const routes = [
{
path: "feature",
component: FeatureRoutes,
},
/* lots of other routes, defined the same way: <Route> wrapped in a component */
];
App.jsx:
<Routes>
{routes.map((route) => (
<Route key={route.path} path={`${pathPrefix}/${route.path}/*`}>
<route.component />
</Route>
))}
</Routes>
This now results in the error above, because the inner routes (for example FeatureRoutes) are wrapped in a functional component. I've tried returning the literal JSX but then get another error. I'm not sure how to fix this: is the only answer to completely rewrite how we define our routes? We also have some routes that are stored in the back-end and map to custom components - again I'm not sure how I can wrap these now I'm not allowed to have a component between Routes and Route.
Any advice appreciated.
I believe a small refactor will get your app rendering again.
In the routes array rename component to Component so it can be rendered as a React component, i.e. as a properly named React component (PascalCased).
const routes = [
{
path: "feature",
Component: FeatureRoutes
}
/* lots of other routes, defined the same way: <Route> wrapped in a component */
];
When mapping the routes render the Component out on the Route component's element prop as JSX.
<Routes>
{routes.map(({ path, Component }) => (
<Route
key={path}
path={`${pathPrefix}/${path}/*`}
element={<Component />}
/>
))}
</Routes>

react router query parameters changed component will unmount

When query parameters changed, the same component will unmount and then mount.for example:
I have a url like /admin and also have a component called Admin. In Admin, there are some inputs for searching. I add a query parameters after /admin like /admin?userId=123.The componet's componentDidMount will excute again. Is there any way to prevent this?
and setting likes this
export default function (history, app) {
return (
<Switch>
<Route exact path='/admin/settings/user' component={getComponent(User,app,userModel)} />
<Route path='/admin/settings/user/:id' component={getComponent(UserEdit,app,userModel)} />
<Route path='/admin/settings/role' component={getComponent(Role,app,roleModel)} />
<Route path='/admin/settings/menu' component=
</Switch>
)
}
getComponent is a layload component.
#Alex Brazh I used v4 and the router likes this;
<Router>
<Switch>
<Route exact path='/' component={getComponent(Login,app,loginModel)}/>
<Route path='/admin' render={ props => (
<Layout>
{ settings(history, app) }
</Layout>
)}/>
<Route path='/finance' render={ props => (
<Layout>
{ finance(history, app) }
</Layout>
)}/>
</Switch>
</Router>
You can use the URL interface to set query string values without unmount and mount your components:
const queryStringValue = 'bar'
const url = new URL(window.location.toString());
url.searchParams.set('foo', queryStringValue);
window.history.replaceState(null, '', url.toString());
Also, this solution won't add a new item in browser navigation stack

react-router render menu when path does not match

I'm using react-router and I want to render a menu component when the user is not in the root and not in the /login path. This is what I have so far
<Route path="/:subpath" component={TopMenuComponent} />
<div>
<Route
exact path="/"
render={props => (
<LoginContainer {...props} setTitle={this.setTitle} />
)}
/>
<Route path='/landing' component={LandingComponent} />
</div>
takes care of not rendering the TopMenuComponent component in the '/' location, however how do I avoid it rendering TopMenuComponent when the user is in the /login path? I could always create another component and wrap it up, but I think that is too much just for this.
Simplest Implementation
Use a ternary expression or short-circuit evaluation to conditionally render your component based on location.pathname, like so:
<Route
render={({ location }) => ['/', '/login'].includes(location.pathname)
? <Component/>
: null
}
/>
Regex Implementation
React Router's matching of path strings relies on path-to-regexp#^1.7.0.
As a result, you can instruct routes to not render for certain paths using regular expressions.
The following implementations should render given any path value, bar "/" and "/login":
// With Regex Inside String.
<Route path={"^(?!.*(\/|\/login)).*$"} component={TopMenuComponent}/>
// With Explicit Regex.
<Route path={new RegExp('^(?!.*(\/|\/login)).*$')} component={TopMenuComponent}/>
Regex in the route path didn't work for me. What worked for me was this. Just add the other condition.
<Route render={({ location }) => {
return location.pathname.indexOf('/login') === -1 ? TopMenuComponent : null
}} />
If you don't wish to use Regular Expressions directly, you can place your login Route in a Switch with the top menu component Route. It will only run the first matching Route and routes without a path attribute match anything.
<div>
<Switch>
<Route
exact path="/"
render={props => (
<LoginContainer {...props} setTitle={this.setTitle} />
)}
/>
<Route path="/:subpath" component={TopMenuComponent} />
</Switch>
<Route path='/landing' component={LandingComponent} />
</div>
For your example, you would need to reorder your divs.
Taken Regex from Arman's answer.
const notInLogin = /^(?!.*(\/login)).*$/
export default () => (
<Router history={history}>
<>
<Route path={notInLogin} component={NavBar} />
<Switch>
<Route exact path="/login" component={Login} />
<Route exact path="/accounts" component={Account} />
<Route exact path="/profile" component={Profile} />
<Route exact path="/" component={Home} />
</Switch>
</>
</Router>
)
If you get PropsType error: https://stackoverflow.com/a/50439120/1099314
Similar to taylor michels answer, but the following accounts for both the '/login' and the '/' (root) routes:
<Route
render={({ location }) =>
location.pathname !== "/" && location.pathname !== "/login" ? (
<TopMenuComponent />
) : null
}
/>>
This also renders the component as a jsx tag <TopMenuComponent />, which works for me where the other approach did not.
You can use useRouteMatch hook
const ParentComponent = props => {
const matched = useRouteMatch(['/', '/login'])
if (matched && matched.isExact) return null
return <ChildComponent {...props} />
}

In react router 4, how does one negate a route / path using regex?

Say i have a route switch statement like the following:
render () {
<pre>
<Switch>
<Route path="/foo" render={render}>
<Route path="/bar" render={renderBar}>
<Route path="/" render={renderHome}>
{/* How do i express everything except the home page ?*/}
<Route render={renderFourOhFour}>
</Switch>
</pre>
}
How do i write a route that excludes everything except the home page given the above example? Do i just write a regex? If so i've tried something like
path={^(?!.*(home))}
with the regex react router v4 tester: https://pshrmn.github.io/route-tester/#/
You can use the render method on the Route, which gets passed the location as a prop. So:
<Route render={({location}) => {
return location.pathname !== '/' ? <p>Not home</p> : ''
}} />
1) It will be visible everywhere except /home
<Route path={/\/(?!home)/} component={Component} />
2) Everywhere except /
<Route path={/^.{2,}$/} component={Component} />
In react router 4, there isn't a explicit way to. I had redesign it in a way that the switch statement as a stack or queue.
It'll match the first few route components as the first choices and you'll have to place the last item as the default.
For example:
<Route path="/" exact component={Home}/>
<Route path="/will-match" component={WillMatch}/>
<Route component={NoMatch} />