Navbar causing other components to not load in React Router - javascript

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.

Related

How to navigate between two pages in React JS

I am using React-Router-Dom to navigate between two javascript elements in a create-react-app dapp but when I use it loads the elements on top of the page and just adds the page to the end of the link (localhost/page).
My goal is to have the page reload to the new page on click of the link button with a new background and styling (already done for that page)
App.js Code
<Router>
<s.Screen>
<Navbar />
<Routes>
<Route path="/MyPandas" element={MyPandas} exact></Route>
</Routes>
/*
Rest of Code
*/
</Router>
My other page is just wrapped in a Fragment Component
<Fragment>
/*
Code
*/
</Fragment>
use exact in your Routes e.g : <Route path='/products' exact>
Assuming your links are working correctly, and it seems they are if the URL in the address bar updates to "/MyPandas".
The issue is that the element prop isn't passed a correct value. react-router-dom#6 Route component's element prop takes a ReactNode, a.k.a. JSX, not a reference to a React component. Pass <MyPandas /> instead of MyPandas.
<Route path="/MyPandas" element={<MyPandas />} />
Note: There's no need for the exact prop. Not only does it not exist in the RRDv6 Route component API, but all routes are now always exactly matched.

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

How to match Route to any path EXCEPT the base (home) path?

I've enjoyed using react-router to conditionally render based on location. For example, I have a component that only renders if I am on the home page, like so:
<Route
exact={ true }
path="/"
render={ HomeHeroSection }
/>
However, because of design requirements, I also have a component that should only be rendered if NOT at the home page/base path. I have only been able to come up with a path-to-regexp pattern that accomplishes this if I name some arbitrary unused parameter, as a sort of wildcard.
<Route
path="/:foo+"
render={ NavLinks }
/>
Is there a better pattern for the path prop that I should be using? Or perhaps a better general react-router paradigm? I've considered Switch, among other things, but I'm curious if I am missing something simple.
If you want the HomeHeroSection component to render only on the root route and the NavLinks component to render only on any other route, this is the use case for Switch. From the docs on Switch:
Switch is unique in that it renders a route exclusively. In contrast, every that matches the location renders inclusively.
So Switch is the right call here.
Here's how you can use it in this case:
<Switch>
<Route exact path='/' component={ HomeHeroSection }/>
<Route component={ NavLinks }/>
</Switch>
Note that the order of the Route components is important here as Switch renders the first child Route that matches the location.
Note that not providing a path attribute to a Route will cause it to match any route (so these should go last in order). If the location that was used to arrive at the NavLinks component matters, you can include the path attribute like normal.
Edit:
Since the two components do not appear in the same place on the page you can use just a plain Route for the HomeHeroSection as you have already, and a Switch that renders null on the root route for the NavLinks component. Here's an example:
<main>
<Route exact path='/' component={ HomeHeroSection }/>
<span>Some other stuff</span>
<Switch>
<Route exact path='/' render={ null }/>
<Route component={ NavLinks }/>
</Switch>
</main>
This will render the NavLinks component for any route other than the root and will render nothing on the root route.

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

Categories