React Router - 404 page always showing due to custom route component - javascript

I have a component which takes in similar props to the standard react-router Route and returns a route. This returned route has a render prop which just passes through the component but adds a nav component. This custom NavRoute does not work with the react-router Switch component, as when I use the custom component inside the switch along with a catch-all 404 page, the page always shows.
How can I make the 404 component only show if none of the other routes match the url?
NavRoute
const NavRoute = ({ exact, path, component: Component }) => {
return (
<Route exact={exact} path={path} render={(props) => (
<div style={styleSideNav}>
<Nav />
<Component {...props} />
</div>
)}/>
);
});
Usage
<Switch>
<NavRoute exact path="/" component={Home} />
<NavRoute exact path="/about" component={About} />
<Route path="/" component={Page404} />
</Switch>
EDIT:
Codesandbox of full example: https://codesandbox.io/s/react-router-stack-overflow-5jcum
Url of codesandobx: https://5jcum.csb.app/invite/abc

Switch only works with the first level of components directly under it. It can't traverse the entire tree.
https://github.com/ReactTraining/react-router/issues/5785#issuecomment-351067856
To fix that, we can pass array of routes instead of routes wrapped in fragment. I have made the changes and updated in codesandbox.
https://codesandbox.io/s/react-router-stack-overflow-hu62w?file=/src/App.js

Related

Navigating between multiple params - React js

So I've been working on a project lately using React js (I thought it would be similar to React native), while I pretty much understand most of it as I previously worked with React native a lot. There are still some new things I'm learning for example the react-router-dom npm. So I understand the basics and how it works, but I'm trying to use parameters which change depending on the user (User ID).
The code below shows how I'm currently using my router. While going to home (/) and /user/:id works, I can't go to /user/:id/settings. If I try going to the settings it renders both the /user/:id page and below it renders settings page.
What I want to be able to do is if the user is in the user/:id page they can click a button which takes them to the user/:id/settings instead of the current issue where it renders the setting page below the user page.
App.jsx
export class App extends React.Component {
render() {
return (
<Router>
<Route exact path="/" component={Home} />
<Route path="/user/:id" component={User} />
<Route path="/user/:id/settings" component={Setting} />
</Router>
)
}
};
User.jsx
render() {
return (
<div>
{/* Cool information about the user */}
<div
className="optionContent"
onClick={() => {
let uri = `/user/${this.props.match.params.id}/settings`;
this.props.history.push(uri)
}}
>
Press me
</div>
</div>
);
}
Extra information:
I have tried using variable parameters for users but I wasn't able to full make those work as once the user enters /user/:id page the buttons update the url but not the parameters in this.
I need to have the ID within the url to fetch from the API and some other stuff
Variable url: /user/:id/:type?
This is because with React Router v5 which is currently the latest version as v6 is completed, the routes aren't exact by default which means that for each of the routes, if the current route starts with the route of a component, this component will be displayed.
For your example:
<Router>
<Route exact path="/" component={Home} />
<Route path="/user/:id" component={User} />
<Route path="/user/:id/settings" component={Setting} />
</Router>
If the current route is "/user/user1" then it only matches the User component.
If the current route is "/user/user1/settings/ then it matches User and Settings components so they will both be rendered as you are finding.
To fix it, simply use the exact keyword on the component with the fewer requirements.
<Router>
<Route exact path="/" component={Home} />
<Route exact path="/user/:id" component={User} />
<Route path="/user/:id/settings" component={Setting} />
</Router>

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.Nested routes

How to make nested routes???
I want initial route to be course/:course_id? after that, when i click on a node I want my url to become course/:course_id?/nodes/:node_id .
I use : "react-router-dom": "^4.2.2"
return (
<Router>
<div id="app-main">
<Header />
<Route path="/course/:course_id?" component = {Content}/>
<Route path="/course/:course_id?/nodes" component = {Content}/>
<Footer />
</div>
</Router>
);
When i click id redirects me to course/nodes and skips :course_id
return(
<div className="paragraph-text-child" onClick={() => this.props.select(chapter)} key={chapter.node_id} >
<Link to="nodes">{chapter.text}</Link>
{this.iterate(chapter.nodes)}
</div>
);
I think you have some concepts mixed up... Route is for handling received URLs, Link is for setting it.
Link does not know about Route, Route does not know about Link. Link sets the URL to what is specified in to. So if your current URL is /course, and to="nodes", the result is /course/nodes. If it was to="0/nodes", the result would be /courses/0/nodes.
Now if I understood correctly, you always want a number between "/courses" and "/nodes", correct?
This can be achieved with Redirect, which comes from react-router-dom too.
If you create the following Route:
<Route path="/courses" render={()=> <Redirect to="/courses/0"/>}/>
And rework the previous route so that course_id is NOT optional
<Route path="/course/:course_id" component={Content}/>
When you navigate to /courses, you will be silently redirected to courses/0. The result is that your Link component with to="nodes" will always redirect to courses/number/nodes - because effectively the courses/ location won't be reachable anymore. Every URL that would not contain a course_id, will be redirected to course_id = 0
Note that these 2 routes should be put in a Switch, and in the correct order, otherwise you will end up redirecting every time...
I have not tested this, but it should do the job:
...
<Header/>
<Switch>
<Route path="/course/:course_id" component={Content}/>
<Route path="/courses" render={()=> <Redirect to="/courses/0"/>}/>
</Switch>
<Footer/>
...
And this should handle /course/course_id
Now, if you want to nest a /course/:course_id/nodes/:node_id Route, that should go into the component rendered by the parent route.
Let's rework our parent Route into this:
<Route path="/course/:course_id" render={(props) => <Content ...props />}/>
What this does is, instead of just rendering the passed component, it renders the component and passed down Router props. Which means that the rendered component will be able to handle routes!
Now, in the Content component:
render() {
return <Route path="/course/:course_id?/nodes/:node_id?" component={NodeContent}/>
}
The last thing we need to do is change the to property of the Link component so that it will redirect to the target node:
<Link to={"nodes/" + chapter.node_id}/>
Does this make sense? I may have missed some gotchas in your code - the idea of what you want to achieve is in here, but you may have to adapt it a bit...

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>

React router call the same code in each route component

I have the next routes on main component of my app:
<div>
<h1><Link to="/">Home</Link></h1>
<Switch>
<Route exact path="/" component={FirstPage} />
<Route exact path="/:language?/second" component={SecondPage} />
<Route exact path="/:language?/account" component={AccountPage} />
<Route exact path="/:language?/add" component={AddNewPage} />
<Route component={NoMatch} />
</Switch>
</div>
I need to add in each child component checking this.props.match.params.language (this is react-router props) in order to set the current language. But white this code in each componentWiilMount looks wired on my mind. Even I put this checking in a single function and will call it every componentWiilMount and pass this.props.match.params.language props to it, anyway it a mess of code. For example, if I have 100 routes I need to add this checking 100 times.
Also, I think about adding this code to the main component lifecycle, and it will be called when the page changed, but I do not have react-router props here.
Maybe you know a better solution for this?
You may want to try nesting your routes. You could have one parent route component that manages the language then nests child route components:
function Root() {
return (
<div>
<h1><Link to="/">Home</Link></h1>
<Route path="/:language?" component={LanguageSelector}/>
</div>
);
}
class LanguageSelector extends React.Component {
componentDidMount() {
// Manage language (if specified)
}
render() {
return (
<Switch>
<Route .../>
<Route .../>
</Switch>
);
}
}

Categories