Why is my dynamic route breaking using Hooks with route parameters? - reactjs

I've been learning React in my spare time, and in doing so I'm building a project management portal. I have a dashboard route setup at "/" and a projects route setup at "/projects". I want to create a dynamic route for single projects, so have defined this route as "/projects/:id".
The route is working when a go through the navigation like so Home > Projects > [Single Project]. This loads the single project component successfully.
The problem is that when I am on a single project (URL example "/projects/test-project") the main navigation breaks, and every link in the sidebar now starts with "/projects". So when in a single project, and clicking on the main navigation, the links are going to:
/projects/dashboard
/projects/projects
/projects/tasks
/projects/contacts
It's almost like when on a single page, the base URL is brought up a level; so when on a single project, all links in the sidebar are based off the relative path of "projects".
As you can probably tell, I'm finding this issue hard to explain. I've looked at various tutorials on this, and it looks like I'm doing everything correctly. Most tutorials are for React 4, so using class components - I've been through so many assumptions, so anything I can provide to further explain the issue or troubleshoot I'm happy to provide.
I am building the app using functional components and hooks, for what it's worth. In terms of my progress with React so far, this is the biggest thing that is causing me confusion, so any guidance on this would be a massive help.
EDIT
Here's an example of my setup, which shows the problem I'm having: https://codesandbox.io/s/funny-mendeleev-ru5vt
So everything seems to be working if I click through the main navigation. If I navigate to a single project, it breaks every other link except dashboard. Can't figure this one out.

I think your are looking for Nested Routes, as you are using react hooks, please change your route configs as below
import React from "react";
import ReactDOM from "react-dom";
import {
BrowserRouter as Router,
Switch,
Route,
Link,
useParams,
useRouteMatch
} from "react-router-dom";
function Topic() {
let { topicId } = useParams();
return (
<div>
<h3>{topicId}</h3>
</div>
);
}
function Topics() {
let { path, url } = useRouteMatch();
return (
<div>
<h2>Topics</h2>
<ul>
<li>
<Link to={`${url}/foo`}>Foo</Link>
</li>
<li>
<Link to={`${url}/bar`}>Bar</Link>
</li>
<li>
<Link to={`${url}/baz`}>Baz</Link>
</li>
</ul>
<Switch>
<Route exact path={path}>
<h3>Please select a topic.</h3>
</Route>
<Route path={`${path}/:topicId`}>
<Topic />
</Route>
</Switch>
</div>
);
}
function App() {
return (
<Router>
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/topics">Topics</Link>
</li>
</ul>
<hr />
<Switch>
<Route exact path="/">
<p>Home</p>
</Route>
<Route path="/topics">
<Topics />
</Route>
</Switch>
</div>
</Router>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
UPDATE:
working demo here

I found the answer. It was something critically simple - I hadn't used forward slashes at the start of my navigation links. Router was working the way it should, but just needed to added slashes.
It really is the simplest of things sometimes. Thank to everyone who helped!

Related

React router v6 and relative links from page within route

Hi am trying to update project using react router to v6. I get the basics but am struggling with relative links.
We have a page that renders reference documentation for a given item by 'id'. The documentation can have links to other 'sibling' material, using sibling id. In other words the user can navigate around the docs without ever leaving the same basic route (parameterised by 'id').
I have made a little repro on codesandbox, with essential code below.
import React from "react";
import {
BrowserRouter,
Routes,
Route,
Link,
useParams
} from "react-router-dom";
import "./styles.css";
function GenericPage() {
const { id } = useParams();
return (
<div className="page">
<p>Document id: {id}</p>
<div>
Links from within page don't work:
<Link to="./foo" className="link">
Foo
</Link>
<Link to="./bar" className="link">
Bar
</Link>
</div>
</div>
);
}
export default function App() {
return (
<BrowserRouter>
<div>
Working top nav:
<Link to="docs/generic/foo" className="link">
Foo
</Link>
<Link to="docs/generic/bar" className="link">
Bar
</Link>
</div>
<Routes>
<Route path="/docs/generic/:id" element={<GenericPage />} />
</Routes>
</BrowserRouter>
);
}
Have tried using ../{id}, etc to no avail.
Maybe this is by design but it seems a bit odd to disallow a link to sibling pages using a simple relative name. All pretty normal in regular web dev.
I'm a bit surprised I hadn't run across this one yet, but it seems to be by design. There's a discussion here that you may find illuminating, or frustrating. There does appear to be some workarounds though.
Apparently the ".." means to "go up one Route". When there's only one Route the code is defaulting to the root "/" "route" that the app is being rendered on.
You can restructure your routes to match the path structure.
Example:
<Routes>
<Route path="/docs">
<Route path="generic">
<Route path=":id" element={<GenericPage />} />
</Route>
</Route>
</Routes>
Now the relative links appear to work as you and I, and I suspect many others, expect them to.
function GenericPage() {
const { id } = useParams();
return (
<div className="page">
<p>Document id: {id}</p>
<div>
Links from within page now work:
<Link to="../foo" className="link">
Foo
</Link>
<Link to="../bar" className="link">
Bar
</Link>
</div>
</div>
);
}
You can nest your parameterized route under the main one, like this:
<Routes>
<Route path="/docs/generic" element={<GenericPage />}>
<Route path=":id" element={<GenericPage />} />
</Route>
</Routes>
The nesting allows the nested path to get tacked onto the end of its parent, so the final route is /docs/generic/:id.
After that, update your in-page links so you only pass the parameter value to the to prop instead of the whole route:
<div>
<Link to="foo" className="link">
Foo
</Link>
<Link to="bar" className="link">
Bar
</Link>
</div>
This allows you to change the actual route value ("/docs/generic") in your router without having to update the links in your GenericPage component.
See it on Codesandbox.
All previous answers are correct and well illustrate the design of the react router v6. However, imho. the proposed solutions are workarounds so that, in this case, the links act as expected.
Much simpler however would be to use the relative="path" flag in the <Link /> component. See the documentation:
By default, links are relative to the route hierarchy, so .. will go up one Route level. Occasionally, you may find that you have matching URL patterns that do not make sense to be nested, and you're prefer to use relative path routing. You can opt into this behavior with relative
This has the big advantage to also function as expected if the component which is rendering the <Link /> component is not in the same hierarchie as the "last" route element, which will often be the case for Layout components.
In your case, simply replace
<Link to="./foo" className="link">
by
<Link to="./foo" relative="path" className="link">

How to use react router to use Hash in URLs

i am new to react and facing a problem. I have a page which contain two tabs. I want to make a hash URL so that it can redirect to corresponding tabs on the basis of url hash. Similarly when i open the page and change a tab, url also updates. Kindly answer in a detailed way as i am new to this and donot know about professional terms.
Moreover i am also bound to use react router for this.
Note: I am using typescript in case it changes something for my solution.
Thanks in advance.
HashRouter uses a hash symbol in the URL, which has the effect of all subsequent URL path content being ignored in the server request (ie you send "www.mywebsite.com/#/person/id" the server gets "www.mywebsite.com". As a result, the server will return the pre # URL response, and then the post # path will be handled by parsed by your client-side react application.
example code :
import React, { Component } from 'react';
import { HashRouter as Router, Route, Link, Switch }
from 'react-router-dom';
import Home from './component/home';
import About from './component/about';
import Contact from './component/contact';
import './App.css';
class App extends Component {
render() {
return (
<Router>
<div className="App">
<ul className="App-header">
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About Us</Link>
</li>
<li>
<Link to="/contact">
Contact Us
</Link>
</li>
</ul>
<Switch>
<Route exact path='/'
component={Home}>
</Route>
<Route exact path='/about'
component={About}>
</Route>
<Route exact path='/contact'
component={Contact}>
</Route>
</Switch>
</div>
</Router>
);
}
}
export default App;

Problems with React-Router

So basically, I have a problem with react router not rendering my SystemSidebar. I want to scroll through my SystemSidebar components, but my problem is when I press on 'LinkSoundIcon' it redirects me to 'a new page' but that page doesnt render my systemSidebar . I want when I press on any of the links of my sidebar that my sidebar remains
import React from 'react'
import './SystemSidebar.css'
import SoundIcon from '#material-ui/icons/Computer';
import ComputerIcon from '#material-ui/icons/Computer';
import { BrowserRouter, Route, Switch, Link } from 'react-router-dom';
import Sound from './Sound';
import Computer from './Computer;
const SystemSidebar=()=> {
return (
<div className='system'>
<div className="sidebar">
<Link to='Sound'><VolumeUpIcon /></Link>
<h4> Sound</h4>
<Link to='Computer'><ComputerIcon /></Link>
<h4> Computer</h4>
</div>
</div>
);
};
import React,{Component} from 'react'
import Sound from './Sound';
import Computer from './Computer';
import SystemSidebar from './SystemSidebar';
class MainSystem extends Component {
render(){
return (
<div className="MAIN">
<BrowserRouter>
<SystemSidebar />
<Switch>
<Route exact path="/" component={SystemSidebar} />
<Route exact path="/Sound" component={Sound}/>
<Route exact path="/Computer" component={Computer}/>
</Switch>
</BrowserRouter>
</div>
);
}
}
export default MainSystem;
<Link to='/Sound'><VolumeUpIcon /></Link>
answer of your first problem and second if you want to access sidebar in each component then don't put it in switch route , simply put it outside the routing... or if u want to access it with specific route then try using nested routing
Okay, so it seems a little wonky with your copy pasting (I hope this is just a problem that stems from copy and pasting and it's not like that in your code). But your Problem is here:
<Route exact path="/Sound" component={Sound}/>
You're saying here that the route should be EXACTLY http://<your root uri>/Sound
You should also use this exact route in the link if you want to hit it, this means you need to have the slash there:
<Link to='/Sound'><VolumeUpIcon /></Link>
Update:
So according to your comment you want the sidebar to stay when you click a link. In this case, take a look at your code:
<Switch>
<Route exact path="/" component={SystemSidebar} />
<Route exact path="/Sound" component={Sound}/>
<Route exact path="/Computer" component={Computer}/>
</Switch>
You define here that the component SystemSidebar will only be loaded when you're at the Root directory ("/") of your App. It will be unloaded when you change that directory, for example, to "/Sound". SystemSidebar will be unloaded and Sound will be loaded instead.
Since your Sidebar should always be shown, it needs to be in your Main App and outside of your actual Router logic. Remember what the React Router does: It switches out components depending on which directory (which Sub-URL) you're in. It's best practice to have a Sidebar, an App Bar or similar things that are always there to be their own components. Your actual content should live in a separate container, where the needed component can be swapped out by the Router if need be. So something like this:
class MainSystem extends Component {
render(){
return (
<div className="MAIN">
<SystemSidebar />
<div className="ContentContainer">
<BrowserRouter>
<Switch>
<Route exact path="/Sound" component={Sound}/>
<Route exact path="/Computer" component={Computer}/>
{/* Route "/" should be last because it acts as fallback! */}
<Route exact path="/" component={StartPage} />
</Switch>
</BrowserRouter>
</div>
</div>
);
}
}
That's pretty basic but I hope you get the gist of it.
Also, I'd encourage you to take a look at an UI framework like Material UI for example. It already provides components ready for use (like your Sidebar which is called Drawer there), it's mobile first and easy to use with responsive design.

Why does React Router not render the previous link again in a nested setup?

I'm working on an user-list project made with React/ React Router for a community I'm part of and I've been running into a problem I can not find the root of.
I have a nested Router set up, to have a userlist appear on the click of a button (url/player) and then - in the userlist - have the profile of that user appear on a click on the name (url/player/:id). This works fine so far!
BUT:
When I'm on a user profile (url/player/:id) and click the link to get back to the userlist, it does not render the userlist-component - though the url in the browser itself changes back to (url/player).
I can't figure out how to make the general userlist reappear again and would surely appreciate some input.
Since the project has multiple components, I separated them into different files, where my problem my lay.
I still tried to reconstruct the instructions of different tutorials for nested Routes. Maybe I'm just overlooking something basic, but I cant seem to find it.
Main Navigation Router in the index.js
<Router>
<Navigation />
<div className="contentBox">
<Route path="/" exact component={Home} />
<Route path="/player" exact component={Playerlist} />
</div>
</Router>;
Userlist Router in the Playerlist-Component
<Router>
<Route path="/player" exact component={Playerlist} />
<Route path="/player/:id" component={Playerprofile} />
</Router>;
The weird thing is, only the Playerlist-Link does not work anylonger. If I click on either the "Home"-Link or any other Navigation, it works. So I assume it has something to do with the rerendering.
I'm sorry if this question seems obvious, I'm still a beginner and appreciate any help! Thank you!
You should improve the nested routing of your app:
No need to wrap PlayerList component with Router. Use Router only in root component.
Don't use Route to PlayerList component within the component itself.
Do use Route to PlayerList component in index.js, but without exact (so routing to '/player/:id' routes work).
Here are snippets of updated code:
App.js (in your case index.js):
export default function App() {
return (
<Router>
<Navigation />
<Route path="/" exact component={ Home } />
<Route path="/player" component={ PlayerList } />
</Router>
);
}
PlayerList.js
export default function PlayerList({ match }) {
return (
<div>
<h1>Player List</h1>
<Route path={`${match.path}/:id`} component={ PlayerProfile } />
</div>
);
}
PlayerProfile.js
export default function PlayerProfile({ match }) {
return <div>Player Profile { match.params.id }</div>;
}
Navigation.js (draft):
export default function Navigation() {
return <nav>
<Link to="/">Home</Link>
<Link to="/player">PlayerList</Link>
<Link to="/player/1">Player 1</Link>
<Link to="/player/2">Player 2</Link>
<Link to="/player/3">Player 3</Link>
</nav>;
}
Though I'd recommend to move the Links to "/player/:id" into PlayerList component.
You can find official notes on nested routing here. Hope that helps.

React router component can't render its own children?

I'm trying to embrace the react-router 4 philosophy of having routing spread throughout the app instead of one central place. For brevity, <App> effectively spits out the following in its render():
<BrowserRouter>
<Link to='/' />
<Link to='/linkb' />
<Switch>
<Route exact path='/' component={ComponentA}>
<Route exact path='/linkb' component={ComponentB>
</Switch>
</BrowserRouter>
...
So far so good: the nav links help me to route to the corresponding components A and B and my 2 page app works fine.
The problem I'm having is that, inside componentB, now I want it to have its own child routes. Though these routes will be 'grandchildren' of <App/>, I see no reason why <App/> should know or care about them. I built this (surrounding content and css stuff mostly ripped out for clarity):
Component B
import React, { Component } from "react";
import { BrowserRouter, Switch, Route, Link } from 'react-router-dom';
import ComponentC from './ComponentC';
import ComponentD from './ComponentD';
export default class ComponentB extends Component {
constructor(props) { super(props);}
render() {
return (
<div>
<div>
<Link to='/linkb/c' className='btn'>link C</Link>
<Link to='/linkb/d' className='btn'>link D</Link>
</div>
{this.props.children}
<Switch>
<Route exact path="linkb/c" component={ComponentC}/>
<Route exact path="linkb/d" component={ComponentD}/>
</Switch>
</div>
);
}
}
When I click link C or link D, the url updates in the browser bar but nothing changes on the page / the new component is not rendered. What am I doing wrong?
OK - so the only way I've gotten this to work is to stop hardcoding the paths in my child components, and use the path segments passed in via react-router. Since these evaluate to the same thing (I think) I'm unsure why this fixes my problem, but it does:
<Link to={`${this.props.match.path}/c`}>link c </Link>
<Link to={`${this.props.match.path}/d`}>link d </Link>
And that works!! No idea why! If someone can explain why I'll accept their answer instead of mine.
update: appears to have been a case of absolute vs relative urls. Using props.match.path avoid that kind of confusion.

Resources