React-router.Nested routes - javascript

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

Related

React Router Routing three levels deep, Only works in App.js file

I am building a website with the following link structure:
/blog
/blog/post1
/preview
The /blog url lists all the blog posts, the /blog/post1 shows the post in edit view and the preview button in the same edit view links to /preview url that shows the preview of the edited version.
My initial idea was to use a /blog/post1/preview url but with the use of <Switch> it became complicated. It did not work because of the whole exact issue where I had to correctly match three urls that start with the same address. So I switched to /preview. Now, I can get the first to urls working but the /preview renders nothing on the screen. Is it because of where my <Link> and <Route> are defined in my app. I have following structure:
<Switch>
<Route path='/blog'>
<Blog/>
</Route>
<Switch>
in the main app.js
<Route exact path='/blog'>
//div with some helper views like search bar
</Route>
//it is inside map function that renders a card view of many posts, post1 for simplicity
<Route path='/blog/post1'>
<PostEditor/>
</Route>
in the main Post.js component
<Route path='/preview'>
//Content
</Route>
in the main PostEditor.js
The PostEditor.js has the Link and Route tags in the same file but not others. Is it a reason that it renders nothing. When I move the Route tag from PostEditor, to App.js, everything works. Is it a nesting issue or something like that. I tried searching online but did not have much luck with multiple levels of nesting, usually it is two. Any help is greatly appreciated.
I am using react-router 5.2.0 and HashRouter as it works better with github
Update:
I have added a sample sandbox to mimic my scenario. Why is the /preview link not rendering anything in this case? I would like to know what I am doing wrong in this case, cause I feel there is a knowledge gap about something about react-router that I am missing about creating Links and Routes at different level? Additionally, what would be the best alternative to handle what I am currently doing.
Code SandBox
I don't see any route nesting here, but I suspect the issue you are running into is with path specificity and order.
Within the Switch you should order your routes to list more specific paths before less specific paths. Recall that the Switch returns and renders the first match is finds, so by trying to match more specific paths first you avoid accidentally matching a less specific path prefix. In other words, "/blog" is a path prefix for the other sub-routes so you want to match it last.
"/blog/:postId/preview"
"/blog/:postId"
"/blog"
Code:
<Switch>
<Route path='/blog/:postId/preview'>
//Content
</Route>
<Route path='/blog/:postId'>
<PostEditor/>
</Route>
<Route path='/blog'>
<Blog/>
</Route>
<Switch>
Edit
Why is the /preview link not rendering anything in this case? I would
like to know what I am doing wrong in this case, cause I feel there is
a knowledge gap about something about react-router that I am missing
about creating Links and Routes at different level?
The "/preview" link renders nothing because the component rendered on "/blog/post1" (i.e. Post) is now no longer being rendered. In other words, the URL is updated to "/preview" and Post is unmounted. Since you've no "/preview" route in the root nothing is matched and rendered.
Solution based on your added codesandbox
You can certainly nest routes. Here are the changes I suggest to properly nest blog routes.
App.js - Specificity and order matters, place more specific root paths before less specific paths.
<Switch>
<Route path="/blog">
<Blog />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
Blog.js - Render nested routes into another Switch component, again paying attention to path specificity and order. Use the useRouteMatch hook to access the current path and url props, path for nested route matching and url for nested links. Notice the route from Post for the preview is moved here.
const Blog = () => {
const { path, url } = useRouteMatch();
return (
<div className="blog">
<Switch>
<Route path={`${path}/:postId/preview`} exact>
Blog Preview Before Save
<br />
</Route>
<Route path={`${path}/post1`}>
<Post />
</Route>
<Route path={path}>
Blog Search Bar
<br />
<Link to={`${url}/post1`}>Post 1</Link>
</Route>
</Switch>
</div>
);
};
Post.js - Again, access the url from useRouteMatch to construct the nested link to the preview route.
const Post = () => {
const { url } = useRouteMatch();
return (
<div className="post">
<Link to={`${url}/preview`}>Preview</Link>
</div>
);
};
Demo
Note: Whether or not you "nest" Route components doesn't really matter in the grand scheme of things, the overall path structure will be identical. You can just as easily (probably easier in fact) define all your routes at the root level, and this is common and perfectly acceptable. The trade off may be in maintenance if you want to "nest" a sub-route elsewhere, you'll need to "find-and-replace" all the particular intermediate path segments versus the "nesting solution" you only need to update the path root/prefix. The "nesting solution" is more dynamic in this sense.
With route /preview we can not identify which blog we are viewing. Pasting a minimum working route configuration bellow
import React, {useState, useEffect} from 'react'
import {Link, BrowserRouter, Switch, Route, useParams} from 'react-router-dom'
#app.jsx
<Switch>
<Route path="/blog" exact>
<Blogs />
</Route>
<Route path="/blog/:id" exact>
<PostEditor />
</Route>
<Route path="/blog/:id/preview">
<Blog />
</Route>
</Switch>
const Blogs = () => {
return(
<h1>Blog list</h1>
)
}
const Blog = () => {
const {id} = useParams()
return(
<h1>Blog {id}</h1>
)
}
const PostEditor = () => {
const {id} = useParams()
return(
<h1>Edit Blog {id}</h1>
)
}
Update:
As I uderstand, You need to display preview right after editing form without a routing switch. For that a button wich trigger show preview element can be used.
import React, {useState} from "react";
import { Route } from "react-router-dom";
import { HashLink as Link } from "react-router-hash-link";
const Post = () => {
const [showPreview, setShowPreview] = useState(false);
const [editData, setEditData] = useState('Some Post attributes')
return(<div className="post">
{showPreview ? <Preview editData={editData} /> : null}
<button onClick={() => {setShowPreview(true)}}>Preview</button>
</div>)
};
Preview = ({editData}) => {
return(
<h1>{editData}</h1>
)
}
export default Post;

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

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

Navbar causing other components to not load in React Router

I am setting up a basic navbar, it just has some buttons, not any complex log or anything. When I have this navbar inside of the switch, no other componet loads, meaning I cannot wrap the navbar between certain components, when I have the navbar outside of the switch , then it works, and I can just check if props.match.location etc is "/" and not load it on homepage, so I found a workaround, but what could be causing this bug, or is it expected behavious, I suspect my wildroutes, but not 100% sure what .
Where I am using navbar
const App = () => {
return (
<div>
<Router>
<Switch>
<Route component={theNavbar} />
<Route exact path="/" component={Home} />
<Route exact path="/api/:city/electronics" component={Electronics} />
<Route exact path="/api/:city/labour" component={Labour} />
<Route exact path="/api/posts/item/:id" component={ItemDetails} />
<Route exact path="/create/:city/:category" component={CreatePost} />
</Switch>
</Router>
</div>
);
};
It's completely expected behavior, and caused by a couple of factors. First is the Switch, and the second is the match anything Route for the nav.
Switch
Renders the first child <Route> or <Redirect> that matches the
location.
All subsequent matches won't render at all.
Route path
Routes without a path always match.
<Route component={theNavbar} />
This route matches all locations and thus will always match. Being listed first within a Switch means it will always render.
The solution to always render some navigation component while still matching other routes is to move that component outside of the Switch but still within the Router.
This is expected behavior. Per the React Router documentation:
Routes without a path always match.
So your NavBar will always be matched in the <Switch> and none of the other components will render.

How to replace a react component with react-router

basically im rendering this react component:
<Grid>
<Him className="him"><Link to="/him">Him</Link></Him>
<Her className="her"><Link to="/her">Her</Link></Her>
</Grid>
essentially just 2 boxes with links to him and her.
I have this route that correctly renders my Categories when you click on him.
<Route
path="/him"
render={() => (
<Categories
products={products}
addToBasket={addToBasket}
/>
)}
/>
However it renders this component underneath the him/her components. It might sound silly but how do I get it to render them over the top or instead of it?
I could something where I set the state of the component to visible: false and toggle that but that seems a bit messy.
all the stuff in this tutorial: https://reacttraining.com/react-router/web/example/no-match, seems to have them routing via header that never disappears, whereas I want to click on links in the body of my application and then show different content. any ideas?
You have to wrap the Grid in a Route as well. You could do something like this
<Route path='/' exact component={LinkComponent} />
And somewhere else in your code:
const LinkComponent = () => (
<Grid>
<Him className="him"><Link to="/him">Him</Link></Him>
<Her className="her"><Link to="/her">Her</Link></Her>
</Grid>
);
This way, if you go to the home route, you see the Grid, and when you click on a Link, you go to corresponding route and the Grid will be hidden.
Ok you just wants to replace the entire content when click on /him or /her, so as Xander Berkein said you should render your links into a main component and then assign it to the / main route, y that way you need to use exact prop to just render that first path that matches to the URL, so
<Switch>
<Route exact path={‘/‘} component={<Links/>} />
<Route exact path={‘/him’} component={<HimComponent/> />
<Route exact path={‘/her’} component={<HerComponent /> />
</Switch>
I hope you find it useful.

ReactJS Navigate on a specific route (Router v4)

So I'm kinda new to ReactJS I would like to achieve the following thing with my code -
const App = () => (
<div>
<Header />
<Route path="/:page" exact component={PageWrapper} />
<Route path="/" exact>
<Redirect to="/home" />
</Route>
</div>
);
So I would like to achieve that if user enters /home, /about, /anythingElse as the link it does not navigate, but if user loads a page without passing page parameter, for example - localhost:3000/ or localhost:3000, then it automatically navigates user to localhost:3000/home .
Currently it also navigates to home if user enters a page. I thought exact meant that exact route has to match for it to load.
Is it possible?
You shouldn’t nest routes in V4.
You can use the Switch component from the router to ensure only one route is matched.
This way all routes that don’t match the /:page route will redirect:
const App = () => (
<div>
<Header />
<Switch>
<Route path="/:page" exact component={PageWrapper} />
<Redirect to="/home" />
</Switch>
</div>
)
Remember to import the Switch component.

Categories