here I am building a website and I am using react-router-dom
everything seems to work fine when I use the Navbar and Footer components in every page.
So I come up with idea to wrap the component in a wrapper which contains Navbar and Footer.
When I click on Link or maybe NavLink, it seems to work fine but when I click again on same or another link in navbar then it navigates to a sub path under the previous selected path.
like this
on Single Click:
http://localhost:3000/projects
on clicking the same link again:
http://localhost:3000/projects/projects
App.js
import './App.css';
import {BrowserRouter as Router, Route, Outlet, Routes} from 'react-router-dom'
import {CommonScreen, Wrapper, About, Email, Projects} from './Barell'
function App() {
return (
<>
<Router>
<Routes>
<Route index element={<Wrapper children={<CommonScreen/>}/>}/>
<Route path='about' element={<Wrapper children={<About/>}/>}/>
<Route path='projects' element={<Wrapper children={<Projects/>}/>}/>
<Route path='email' element={<Email/>}/>
</Routes>
</Router>
<Outlet/>
</>
);
}
export default App;
Navbar.jsx:
import React from 'react'
import '../../index.css'
import { NavLink } from 'react-router-dom'
const links = [
{
name:"Home",
slug:"/",
},
{
name:"Projects",
slug:"projects",
},
{
name:"About",
slug:"about",
},
{
name:"Contact",
slug:"email",
},
]
export const Navbar = () => {
let activeStyle = {
textDecoration: "underline",
};
return (
<>
<nav>
<div id='brand'>Prema<span>culture</span></div>
<ul id='links'>
{
links.map((current,index) => (
<li>
<NavLink
key={index}
to={current.slug}
style={({ isActive }) =>
isActive ? activeStyle : undefined
}
>
{current.name}
</NavLink>
</li>
))
}
</ul>
</nav>
</>
)
}
Wrapper.jsx:
import React from 'react'
import { Navbar, Footer } from '../Barell'
export const Wrapper = ({children}) => {
return (
<>
<Navbar/>
{children}
<Footer/>
</>
)
}
You are using relative paths (versus absolute), so when already on a route, say "/projects", and then click a link that navigates to "about", the code links relative from "/projects" and results in "/projects/about".
To resolve you can make all the link paths absolute by prefixing a leading "/" character. Absolute paths begin with "/".
Example:
const links = [
{
name:"Home",
slug:"/",
},
{
name:"Projects",
slug:"/projects",
},
{
name:"About",
slug:"/about",
},
{
name:"Contact",
slug:"/email",
},
];
Additionally, to make the code more DRY you might also want to convert the Wrapper component into a layout route. Layout routes render an Outlet component for nested routes to be rendered into instead of a children prop for a single routes content.
Example
import React from 'react';
import { Outlet } from 'react-router-dom';
import { Navbar, Footer } from '../Barell';
export const Layout = () => {
return (
<>
<Navbar/>
<Outlet />
<Footer/>
</>
)
}
...
function App() {
return (
<>
<Router>
<Routes>
<Route element={<Layout />}>
<Route path="/" element={<CommonScreen />} />
<Route path='about' element={<About />} />
<Route path='projects' element={<Projects />} />
</Route>
<Route path='email' element={<Email />} />
</Routes>
</Router>
<Outlet />
</>
);
}
Related
My sidebar uses react-router-dom to switch between page files using routes, however when I switch routes, my page reloads. Is there a way I can switch routes without reloading the page?
SideBar
import React from 'react'
import {SideNav_Data} from "./SideNav_Data"
import Sunrise from './Sunrise.png'
function SideNav() {
return (
<div className = "SideNav">
<img src={Sunrise} className='SideNavImg' alt=''/>
<ul className="SideNavList">
{SideNav_Data.map((val, key) => {
return (
<li key={key}
className = "row"
id={window.location.pathname === val.link ? "active" : ""}
onClick = {() => {
window.location.pathname = val.link;
}}>
<div id='icon'>{val.icon}</div>{" "}<div id='title'>{val.title}</div>
</li>
)
})}
</ul>
</div>
)
}
export default SideNav
Sidebar Data
import React from 'react'
import CottageIcon from '#mui/icons-material/Cottage';
import DirectionsCarFilledIcon from '#mui/icons-material/DirectionsCarFilled';
import PersonIcon from '#mui/icons-material/Person';
import AccountBalanceIcon from '#mui/icons-material/AccountBalance';
import AccountCircleIcon from '#mui/icons-material/AccountCircle';
import ContactSupportIcon from '#mui/icons-material/ContactSupport';
export const SideNav_Data = [
{
title: "Home",
icon: <CottageIcon />,
link: "/"
},
{
title: "Cars",
icon: <DirectionsCarFilledIcon />,
link: "/Cars"
},
{
title: "Representatives",
icon: <PersonIcon />,
link: "/Representatives"
},
{
title: "Loan Estimator",
icon: <AccountBalanceIcon />,
link: "/Loan-Estimator"
},
{
title: "Account",
icon: <AccountCircleIcon />,
link: "/Account"
},
{
title: "Support",
icon: <ContactSupportIcon />,
link: "/Support"
},
]
Home page
import React from 'react'
import SideNav from '../Components/SideNav'
function Home() {
return (<div>
<SideNav />
</div>
)
}
export default Home
App.js
import './App.css';
import { BrowserRouter as Router, Routes, Route} from "react-router-dom"
import Home from './Pages/Home';
import Cars from './Pages/Cars';
import Reps from './Pages/Reps';
import LoanEst from './Pages/LoanEst';
import Account from './Pages/Account';
import Support from './Pages/Support';
function App() {
return (
<Router>
<Routes>
<Route path='/' element={<Home />}/>
<Route path="/Cars" element={<Cars />}/>
<Route path="/Representatives" element={<Reps />}/>
<Route path="/Loan-Estimator" element={<LoanEst />}/>
<Route path="/Account" element={<Account />}/>
<Route path="/Support" element={<Support />}/>
</Routes>
</Router>
)
}
export default App;
I've tried switching the "link: " part of the sidebar data to <Link to={Home /}/> but that resulted in the sidebar completely disappearing.
You should use the Link component instead of changing window.location.pathname:
{SideNav_Data.map((val, key) => {
return (
<Link
key={key}
className = "row"
id={window.location.pathname === val.link ? "active" : ""}
to={val.link}
>
<div id='icon'>{val.icon}</div>{" "}<div id='title'>{val.title}</div>
</Link>
)
})}
Issues
Using window.location.pathname to set the current URL path will necessarily trigger a page reload and remount the entire app. Use one of the link components exported from react-router-dom to issue declarative navigation actions within the app.
The SideNav component disappears because it is only rendered when the Home component is rendered when the path is "/". Use a layout route to render the SideNav component
Solution
I suggest importing and using the NavLink component as it's a special version of the Link component that applies an "active" classname by default. If you need to compute the id attribute for the li element then use the useLocation hook to access the current location.pathname value.
Example:
import { NavLink, useLocation } from 'react-router-dom';
...
function SideNav() {
const { pathname } = useLocation();
return (
<div className="SideNav">
<img src={Sunrise} className='SideNavImg' alt='' />
<ul className="SideNavList">
{SideNav_Data.map((val) => (
<li
key={val.link}
className="row"
id={pathname === val.link ? "active" : ""}
>
<NavLink to={val.link} end>
<div id='icon'>{val.icon}</div>
{" "}
<div id='title'>{val.title}</div>
</NavLink>
</li>
))}
</ul>
</div>
);
}
Render the SideNav as part of a layout route so it's conditionally rendered with the routes you want it to be rendered with.
Example:
import {
BrowserRouter as Router,
Routes,
Route,
Outlet,
} from "react-router-dom"
import Home from './Pages/Home';
import Cars from './Pages/Cars';
import Reps from './Pages/Reps';
import LoanEst from './Pages/LoanEst';
import Account from './Pages/Account';
import Support from './Pages/Support';
import SideNav from './Components/SideNav';
const SideNavLayout = () => (
<>
<SideNav />
<Outlet />
</>
);
function App() {
return (
<Router>
<Routes>
<Route element={<SidNavLayout />}>
<Route path='/' element={<Home />} />
<Route path="/Cars" element={<Cars />} />
<Route path="/Representatives" element={<Reps />} />
<Route path="/Loan-Estimator" element={<LoanEst />} />
<Route path="/Account" element={<Account />} />
<Route path="/Support" element={<Support />} />
</Route>
</Routes>
</Router>
);
}
Home
import React from 'react';
function Home() {
return (
<div>
...
</div>
);
}
export default Home;
I am doing a simple application which has a protected route.
The code goes like:
function App() {
return (
<>
<Router>
<Header />
<Routes>
<Route path="/" element={<Home />} />
<Route
path="/protected"
element={
<PrivateRoute>
<ProtectedExample />
</PrivateRoute>
}
/>
<Route path="/sign-in" element={<Login />} />
</Routes>
</Router>
</>
);
}
Private Route is :
import { Navigate } from "react-router-dom";
import { useAuthStatus } from "../hooks/useAuthStatus";
import Spinner from "./Spinner";
const PrivateRoute = ({ children }) => {
const { loggedIn, checkingStatus } = useAuthStatus();
if (checkingStatus) {
return <Spinner />;
}
return loggedIn ? children : <Navigate to="/sign-in" />;
};
export default PrivateRoute;
Protected Route
import React from "react";
const ProtectedExample = () => {
return <div>ProtectedExample</div>;
};
export default ProtectedExample;
Header.js
import React from "react";
import { Container, Nav, Navbar } from "react-bootstrap";
import { Link } from "react-router-dom";
const Header = () => {
return (
<Navbar bg="light" variant="light" fixed="top">
<Container>
<Navbar.Brand>
<Link to="/">Vivans</Link>
</Navbar.Brand>
<Nav className="me-auto">
<Link to="/sign-in" className="me-4">
Login
</Link>
<Link to="/protected">ProtectedRoute</Link>
</Nav>
</Container>
</Navbar>
);
};
export default Header;
When i remove the header component in App.js
The output is :
But when i add header component in App.js
The output is :
It doesn't return ProtectedExample as expected to be .
Why?
Actually, it might be rendering it. It maybe invisible for us because of CSS styling.
<Navbar bg="light" variant="light" fixed="top">
In the above line, you have given the NavBar to be fixed at the top. So the ProtectedExample would be behind the NavBar
Refer: https://developer.mozilla.org/en-US/docs/Web/CSS/position
Try changing your component like this,
const ProtectedExample = () => {
return <div style="margin-top: 3em">ProtectedExample</div>;
};
This might bring the content downwards to make it visible.
I'm trying to create triple nesting with react-router but it's not working.
Here is the expected output
I want to perform but I am trying to implement this with the router so my tab should get switch according to URL passed to it.
When the user just write / in the URL it should redirect to login page (working)
When the user write /dashboard it should redirect to the dashboard with side list (working)
When the user click on list items such as about in drawer of the dashboard then it should render about page.
Now my about page contains two tabs one for general and one for account (Everything was working until I implement this feature now the whole app has been crashed and not even showing the error)
Here is my code in sandbox. Note: I have commented on my third nested route because it was crashing entire app
App.js
import { Switch, Route } from "react-router-dom";
import Login from "./pages/Login";
import Dashboard from "./pages/Dashboard";
import "./styles.css";
export default function App() {
return (
<div className="App">
<Switch>
<Route path="/dashboard" component={Dashboard} />
<Route path="/" exact component={Login} />
</Switch>
</div>
);
}
Dashboard.js for nested routing
import { Switch, Redirect, Route, useRouteMatch } from "react-router-dom";
import Home from "./Home";
import About from "./About";
import AppDrawerBar from "../components/AppDrawerBar";
const Dashboard = () => {
const { path } = useRouteMatch();
return (
<>
<AppDrawerBar>
<Switch>
<Route path={`${path}/about`} component={About} />
<Route path={`${path}/home`} component={Home} />
<Redirect to={`${path}/home`} />
</Switch>
</AppDrawerBar>
</>
);
};
export default Dashboard;
About.js third nested routing to handle tab navigation after clicking on about list item
import { Switch } from "#material-ui/core";
import React from "react";
import { Redirect, Route } from "react-router-dom";
import TabMenu from "../components/TabMenu";
const About = (props) => {
return (
<>
<h1>Welcome to about</h1>
{/* This doesn't work.. this code is to render tab navigation */}
{/* <Switch>
<Redirect exact from={`${props.path}/about`} to="about/general" />
<Route
exact
path="/about/:page?"
render={(props) => <TabMenu {...props} />}
/>
</Switch> */}
</>
);
};
export default About;
TabMenu.js to handle my tabs
import { Paper, Tab, Tabs } from "#material-ui/core";
import React, { useState } from "react";
import General from "../pages/General";
import Account from "../pages/Account";
const TabMenu = (props) => {
const { match, history } = props;
const { params } = match;
const { page } = params;
const tabNameToIndex = {
0: "about",
1: "contact"
};
const indexToTabName = {
about: 0,
contact: 1
};
const [selectedTab, setSelectedTab] = useState(indexToTabName[page]);
const handleChange = (event, newValue) => {
history.push(`/about/${tabNameToIndex[newValue]}`);
setSelectedTab(newValue);
};
return (
<>
<Paper elevation={2}>
<Tabs
value={selectedTab}
onChange={handleChange}
indicatorColor="primary"
textColor="primary"
>
<Tab label="General" />
<Tab label="Profile" />
</Tabs>
</Paper>
{selectedTab === 0 && <General />}
{selectedTab === 1 && <Account />}
</>
);
};
export default TabMenu;
I have some problem with react-router. It doesn't go to Edit page when I click on id. The id is in URL, but it doesn't do anything.
const Main = (props) => {
const { pathname } = props.location;
return (
<Fragment>
<div>
<div className="container">
<Header />
{pathname === "/create" ? <Create /> : null}
{pathname === '/edit/:id' ? <Edit /> : null}
{pathname === "/" ? <Home /> : null}
</div>
</div>
</Fragment>
);
};
export default withRouter(Main);
app.js:
require('./components/Index');
import React from 'react'
import ReactDOM from "react-dom"
import Index from "./components/Index";
import { BrowserRouter as Router} from "react-router-dom";
import { ToastContainer } from 'react-toastify';
const App =() =>{
}
if (document.getElementById('app')) {
ReactDOM.render(<Router> <Index /> <ToastContainer /></Router>, document.getElementById('app'));
}
index.js:
import React from "react";
import { Route, Switch } from "react-router-dom";
import Main from "./CRUD/Main";
import Create from "./CRUD/Create";
import Edit from "./CRUD/Edit";
import Home from "./CRUD/Home";
const Index = (props) => {
return (
<Main>
<Switch>
<Route path="/Create" component={Create} />
<Route path='/edit/:id' component={Edit} />
<Route exact path="/" component={Home} />
</Switch>
</Main>
);
};
export default Index;
I think main.js have some problems with pathname.
You don't need to do conditional rendering like you are doing in Main when using react-router. Switch will automatically render the first child <Route> or <Redirect> that will match the location.
Hence, you need to remove Main from your router component i.e. Index so that it looks like as shown below:
const Index = (props) => {
return (
<>
<NavBar /> {/* NavBar is optional, I just added for example */}
<Switch>
<Route path="/create" component={Create} />
<Route path="/edit/:id" component={Edit} />
<Route exact path="/" component={Home} />
</Switch>
</>
);
}
NavBar: (Links; just for example)
function NavBar() {
return (
<ul>
<li>
<Link to="/create">Go to create</Link>
</li>
<li>
<Link to="/edit/123">Go to edit with id = 123</Link>
</li>
<li>
<Link to="/">Go to home</Link>
</li>
</ul>
);
}
Now, when you click on the above links, it will automatically take you to the related component (as declared in routes i.e. Index). (no manually condition checking)
And example, to retrieve the URL param i.e. id in Edit component using useParams hook:
function Edit() {
const { id } = useParams<{ id: string }>(); // Remove <{ id: string }> if not using TypeScript
return <h2>Edit, {id}</h2>;
}
I can get the data of the players when i Route to the Players Component,
but when i click on the Link Tags, the PLayersContainer Component is not opening.
This is my App.js File.
import React, { Component } from 'react'
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'
import Players from './Components/Players'
import PlayersContainer from './Components/Container/playersContainer'
import Navigation from './Components/Navigation';
export default class App extends Component {
state = {
players:[
{
id:1,
name:'Ronaldo'
},
{
id:2,
name:'Messi'
}
]
}
render() {
return (
<Router>
<Navigation />
<Switch>
<Route path="/players" render={(props) => <Players {...props} players={this.state.players} />} />
<Route exact path="/players/:id" render={PlayersContainer} />
</Switch>
</Router>
)
}
}
This is my Players Component.
import React from 'react'
import { Link } from 'react-router-dom'
export default function Players(props) {
const renderPlayers = () => {
let players = props.players.map(playerObj => <li> <Link to={`/players/${playerObj.id}`}> Player {playerObj.name} </Link></li>)
return players
}
return (
<div>
<ul>
{renderPlayers()}
</ul>
</div>
)
}
This is my PlayersContainer Component, where i want to render the individual data of the Player.
import React from 'react'
import { Link } from 'react-router-dom'
export default function PlayersContainer(props) {
const renderPlayers = () => {
console.log(props);
}
return (
<div>
<ul>
{renderPlayers()}
</ul>
</div>
)
}
You have the wrong Route marked as exact. The way its written currently, anything beginning with /players will match the first route. Since its in a switch, only the first match will be rendered.
Change it from:
<Route path="/players" render={(props) => <Players {...props} players={this.state.players} />} />
<Route exact path="/players/:id" render={PlayersContainer} />
to this:
<Route exact path="/players" render={(props) => <Players {...props} players={this.state.players} />} />
<Route path="/players/:id" render={PlayersContainer} />
Now only exactly /players will match the first route, and /players/id can continue past it to match the second.