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

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>
</>
)
}

Related

Is there a function in react to hide a component based on the website path?

In my react app I currently have this:
<Router>
<div class Name="App">
<Route path="/" exact component={PersonList} />
<Route path="/rules" exact component={RulesPage} />
<Route path="/roles" exact component={RolesPage} />
<Route path="/test" exact component={Test} />
<Footer />
</div>
</Router>
However I want the footer element to be hidden if the route path is "/test"
It would be a lot cleaner than writing:
<Route path="/roles" exact component={Footer} />
<Route path="/rules" exact component={Footer} />
<Route path="/" exact component={Footer} />
If anyone knows the function to do this it would be greatly appreciated.
You could create a higher-order component that renders a component with a footer and then you could render that higher-order component at all the paths other than /test.
Higher-order component just takes a component that should be displayed with a Footer component and returns another component that just renders the wrapped component along with the Footer component.
function WithFooter(WrappedComponent) {
const EnhancedComponent = (props) => {
return (
<>
<WrappedComponent {...props} />
<Footer />
</>
);
};
return EnhancedComponent;
}
After this, instead of exporting PersonList component, you need to export the component returned by calling WithFooter higher-order component as shown below:
function PersonList() {
...
}
export default WithFooter(PersonList);
You need to do the same for other components as well that should be rendered with a Footer.
With higher-order component all set-up, your routes definition don't need to change:
<Router>
<Route path="/" exact component={PersonList)} />
<Route path="/rules" exact component={RulesPage} />
<Route path="/roles" exact component={RolesPage} />
<Route path="/test" exact component={Test} />
</Router>
Alternative solution is to conditionally render the Footer component after checking the URL using window.location or useParams() hook provided by react-router-dom but useParams() will only work if your component is rendered using react router. In your case, you will need window.location.
In your Footer component you could just check if the window.location.pathname includes /test and just return null
Another option incase you are not familiar with the HOC pattern is to render the <Footer/> component inside only those components that need it rather than at the top level.

React router remounts children while changing routes (without reconciliation)

I have a situation where few routes consists of the same component and I want to preserve its state, but with this behavior it's fairly impossible.
Live example (look at seconds elapsed):
https://csb-43lp1km647-nkiinaibit.now.sh
CodeSandbox:
https://codesandbox.io/s/43lp1km647
Of course I could save and restore state of timer while changing routes, but my app has an infinite CSS transition as a background (not on every route).
I was trying adding keys, even portal, but it still remounts everything.
Is there any way to enforce typical React's reconciliation (remount only elements that have to be remounted)?
Update:
Just found out that <Switch /> will render everything until it finds element that matches, and that could be either a certain <Route /> or any element except it's inside <Fragment />.
Code:
<Router history={history}>
<Switch>
<Route path="/third" component={RouteWithoutTimer} />
<React.Fragment>
<Timer />
<Route exact path="/" component={FirstRoute} />
<Route path="/second" component={SecondRoute} />
</React.Fragment>
</Switch>
</Router>
Or with wildcard route:
<Router history={history}>
<Switch>
<Route path="/third" component={RouteWithoutTimer} />
<React.Fragment>
<Timer />
<Switch>
<Route exact path="/" component={FirstRoute} />
<Route component={NotFoundRoute} />
</Switch>
</React.Fragment>
</Switch>
</Router>
It's not a solution I was looking for, but it works fine.
You are mounting a new instance of Timer in each route.
If you want to share a component across routes, you should lift it to the parent component of the routes:
<React.Fragment>
<Timer />
<Switch>
<Route exact path="/" component={FirstRoute} />
<Route path="/second" component={SecondRoute} />
</Switch>
</React.Fragment>
This is where the state management libraries come to scene.
In React, states are per component and has lifecycle of it's owned component, so when router destructs/remounts the component, you lose all the data. I advice you to migrate your state to a state management library like redux as soon as possible. Otherwise your code can be mess if this is a medium-sized application.

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>

Wrap a React route render function in a HOC

I am using React-Router 4 to create some routes. (It uses the PrivateRoute component from the official docs but that's not relevant to this question.)
<HashRouter>
<Switch>
<PrivateRoute exact path='/' component={Wrapper(Home)} />
<Route exact path='/login' render={(props) => <Login authenticate={this.authenticate} {...props} />} />
<PrivateRoute exact path='/clients' component={Wrapper(Clients)} />
</Switch>
</HashRouter>
As you can see the Home and Clients components are wrapped in an HOC. For the moment this does nothing:
const Wrapper = (Page) => {
return (props) => (
<div className="page">
<p>This should be on every page</p>
<Page {...props} />
</div>
);
}
This works fine. What I can't figure out is how to wrap the Login route in the same HOC. I've tried
<Route exact path='/login' render={(props) => Wrapper(<Login authenticate={this.authenticate} {...props} />)} />
But this returns the error:
Route.render(): A valid React element (or null) must be returned.
Can you try doing it like that:
<Route exact path='/login' render={(props) => Wrapper(Login)({...props, authenticate: this.authenticate})} />
Because now Wrapper HOC is returning a function, but the react element is expected. To get the react element we'll need to call this function with the needed props and that seems like a good place to add that extra prop this.authenticate.
Here's a quick snippet I made:
https://stackblitz.com/edit/react-zestnq

Render component of nested routes ReactJS

I have nested routes that go 3 levels deep but I'm not able to render the component of my last route. This is how I structured my routes :
<Provider store={store}>
<Router history={browserHistory}>
<Route path="/login" component={Login} />
<Route path="/" name="test" component={requireAuthentication(App)}>
<Route path="/modules" name="Modules" component={Modules}>
<Route path="virtual-inputs" name="Virtual Inputs" component={VirtualInput}>
<Route path="add" name="Add new virtual input" component={AddVirtualInput}/>
</Route>
</Route>
</Route>
</Router>
</Provider>
When I go to modules/virtual-inputs/add I get my component VirtualInput rendered.
I render inside modules my child components with this.props.children, but how can I render my component AddVirtualInput when going to /modules/virtual-inputs/add ?
I saw already on another thread (https://stackoverflow.com/a/33690586/846326) that another solution is to have my routes like this :
<Route path="/modules" name="Modules" component={Modules}>
<Route path="virtual-inputs" name="Virtual Inputs" component={VirtualInput}/>
<Route path="virtual-inputs/add" name="Add new virtual input" component={AddVirtualInput}/>
</Route>
Is this the way to do it, meaning we can't go further then 2 levels?
There is no restriction on the level of nesting of routes in React Router.
When you nest routes, the parent route component will receive the current matching child route component as this.props.children. As long as you don’t forget to use this.props.children, they should render just fine.
In your case, it should be enough to make sure that you use this.props.children in the render() method of the VirtualInput component. Otherwise it will receive AddVirtualInput in this.props.children but you won’t see it since it doesn’t get rendered.
If you already did that, please share the full code of the components to further diagnose the issue.

Categories