I am trying add a custom styling to the active route inside the navigation component inside the dashboardlayout, but I am not able to get the current route inside the current component.
const Index = props => {
return (
<BrowserRouter>
<DashboardLayout>
<Route exact path='/' component={Dashboard} />
<Route exact path='/earnings' component={Earnings} />
<Route exact path='/comms' component={Comms} />
<Route exact path='/rankings' component={Rankings} />
<Route exact path='/ratings' component={Ratings} />
<Route exact path='/ads' component={Ads} />
<Route exact path='/settings' component={Settings} />
<Route exact path='/ad/details' component={AdDetails} />
<Route exact path='/ad/submit-sample' component={SubmitSample} />
<Route exact path='/feedback' component={Feedback} />
</DashboardLayout>
</BrowserRouter>
);
};
export default Index;
I made a little component that looks like this:
import React from 'react';
import { Route, Link } from 'react-router-dom';
const Nav = ({
children,
to,
exact,
onClick,
className,
...rest
}) => (
<Route
path={to}
exact={exact}
children={({ match }) => (
<Link
// Assign class and active class name
className={match ? `${className} act-link` : className}
to={to}
replace={match && to === match.path}
onClick={(e) => {
// Avoid clicking the current route
if (!match || to !== match.path) {
onClick(e);
}
}}
{...rest}
>
{children}
</Link>
)}
/>
);
You can use it this way:
const NavList = ({
links,
toggleOpened,
}) => (
<ul className="main-menu">
{
links.map((link) => (
<li
key={link.name}
className={link.className}
>
<NavLink
className="icon-w"
to={link.url}
onClick={(e) => {
e.target.focus();
toggleOpened(false);
}}
>
<div className={`os-icon ${link.icon}`} />
</NavLink>
</li>
))
}
</ul>
);
Hope it helps
Related
I use 6.8.1 react-router-dom and react is 18.0.9.
How I can render the page in nested route condition (for example: render the page, MemberExample1 in MemberDetail, once it is clicked)?
Here is how the logic flow. It works until I click and (expected) render the page.
MemberMgt.tsx ->
opened MemberDetail.tsx (from MemberMgt.tsx) ->
Click MemberNavbar's one of button. (Should render the page based on its link and path ON MemberDetail.tsx.)
src > routers.tsx
const Routers = (): JSX.Element => {
const location = useLocation();
return (
<Routes location={location}>
<Route path="/" element={<Main />} />
<Route path="/designsystem" element={<DesignSystem />} />
<Route path="/modal" element={<ModalPage />} />
<Route path="*" element={<NotFound />} />
<Route path="/servermgt" element={<ServerMgt />} />
<Route path="/membermgt">
<Route path="" element={<MemberMgt />} />
<Route path="detail/:id/*">
{/* MemberDetail is the page I should render button when clicked. */}
<Route path="" element={<MemberDetail />} />
</Route>
</Route>
MemberMgt.tsx
src > pages > MemberMgt
return (
// note: viewList is custom hook. This state refers to the custom hook's state.
{_.map(state.viewList, (item, key) => {
<tr
key={key}
onClick={(e) => {
console.log(item.active);
onChoice(item.id);
navigate(`detail/${String(item.data.idx)}`, { state: item.data });
}}
>
MemberDetail.tsx -
This file act as a middle page to display the other page once the button is clicked.
src > pages > MemberMgt> MemberDetail.tsx
import MemberBrief from './MemberDetailMenu/MemberBrief/MemberBrief';
import MemberExample1 from './MemberDetailMenu/MemberMsMed/MemberExample1';
import MemberExample2 from './MemberDetailMenu/MemberPoint/MemberExample2';
import MemberExample3 from './MemberDetailMenu/MemberWarn/MemberExample3';
import MemberNavbar from './MemberDetailMenu/MemberNavbar';
const MemberDetail = () => {
// this state does not refer to the custom hook!
const { state } = useLocation();
return (
<div className="title-box">
<h5>Main - Detail Page</h5>
</div>
<>
<div className="detail-box-detail">
<div className="detail-global-container-gray">
<div className="memberNavbar">
<MemberNavbar />
</div>
{/* nested Route */}
<Routes>
<Route path="*" element={<MemberBrief />} />
{/* <Route path="/brief" element={<MemberBrief />} /> */}
<Route path="/example1" element={<Example1 />} />
<Route path="/example2" element={<Example2 />} />
<Route path="/example3" element={<Example3 />} />
</Routes>
</div>
</div>
</>
)}
Lastly, this is the button, MemberNavbar.tsx.
src > pages > MemberMgt > MemberDetailMenu > MemberNavbar
const MemberNavbar = () => {
const navigate = useNavigate();
const param = useParams();
const handleClickBrief = () => {
navigate(`/membermgt/detail/${param.id as string}`);
};
const handleClickExample1 = () => {
navigate(`/membermgt/detail/${param.id as string}/example1`);
};
const handleClickExample2 = () => {
navigate(`/membermgt/detail/${param.id as string}/example2`);
};
const handleClickExample3 = () => {
navigate(`/membermgt/detail/${param.id as string}/example3`);
};
return (
<div className="detail-button">
{/* <Button style="filled" size="md" onClick={handleInfoClick}> */}
<Button onClick={handleClickBrief} style="transparent" size="md">
Main - detail page
</Button>
<Button onClick={handleClickExample1} style="transparent" size="md">
Example1
</Button>
<Button onClick={handleClickExample2} style="transparent" size="md">
Example2
</Button>
<Button onClick={handleClickExample3} style="transparent" size="md">
Example3
</Button>
</div>
);
};
Trying to teach myself react and stuck on one part... I can't seem to get page specific content to display based on URL using useLocation() -- HELP!
App.js - router displays page on click, yay!
<Router>
<Routes>
<Route exact path="/" element={<Home />} />
<Route path="/project/projectOne" element={<Project />} />
<Route path="/project/projectTwo" element={<Project />} />
</Routes>
</Router>
Project.js - Project template serves up the components as expected
const Project = () => {
return (
<div className='content-wrapper'>
<Scroll />
<ProjectIntro />
<ProjectContent />
<ProjectGrid />
<Contact />
</div>
); }; export default Project;
ProjectIntro.js - A component trying to serve up the content -- this is where I'm stuck, useLocation() see's the path, but I can't figure out how to show the "projectIntroDetails" based on that path.
const projectOne = () => {
<h1 className='project-intro-heading'>Title Here</h1>,
<figure className='project-intro-image'>
<img src={projectImage} alt='placeholder'/>
</figure>
}
const projectTwo = () => {
<h1 className='project-intro-heading'>Title Here</h1>,
<figure className='project-intro-image'>
<img src={projectTwoImage} alt='placeholder' />
</figure>
}
const projectIntroDetails = {
projectOne: {
component: <projectOne />
},
projectTwo: {
component: <projectTwo />
}
}
const ProjectIntro = () => {
const projectPath = useLocation();
console.log(projectPath);
// this is where I need help
// how do I turn the path into seeing details to render the correct content?
const projectIntroDetail = projectIntroDetails[projectPath.pathname.split("/project/")];
return (
<div className='project-intro'>
{projectIntroDetail}
</div>
);
}; export default ProjectIntro;
You can use a component with a switch statement to determine which child component to render. This method allows you to pass any additional props to the child components.
If you don't need the <div className='project-intro'> element, you could also render the switch directly inside your ProjectIntro component.
const ProjectOne = () => {
<h1 className='project-intro-heading'>Title Here</h1>,
<figure className='project-intro-image'>
<img src={projectImage} alt='placeholder'/>
</figure>
}
const ProjectTwo = () => {
<h1 className='project-intro-heading'>Title Here</h1>,
<figure className='project-intro-image'>
<img src={projectTwoImage} alt='placeholder' />
</figure>
}
const ProjectIntros = ({ slug, ...props }) => {
switch(slug) {
case 'projectOne':
return <ProjectOne {...props} />;
case 'projectTwo':
return <ProjectTwo {...props} />;
default:
return null;
}
}
const ProjectIntro = () => {
const projectPath = useLocation();
console.log(projectPath);
return (
<div className='project-intro'>
<ProjectIntros slug={projectPath.pathname.split("/")[2]} />
</div>
);
}; export default ProjectIntro;
You don't really need to use the useLocation hook or pathname value to handle any conditional rendering logic, that's what the routing components are for.
I would suggest either passing in the correct sub-project component as a prop to be rendered on the correctly matching route, or refactoring the routes to do this in a more "react router" way.
Passing component down as prop example:
App
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route
path="/project/projectOne"
element={<Project projectIntro={<ProjectOne />} />}
/>
<Route
path="/project/projectTwo"
element={<Project projectIntro={<ProjectTwo />} />}
/>
</Routes>
</Router>
Project
const Project = ({ projectIntro }) => {
return (
<div className='content-wrapper'>
<Scroll />
<div className='project-intro'>
{projectIntro}
</div>
<ProjectContent />
<ProjectGrid />
<Contact />
</div>
);
};
Using react-router-dom to your advantage.
Project
Convert Project into a layout component and render the ProjectOne and ProjectTwo components on nested routes. Layout routes are intended to be used to share common UI elements and layout, and render routed content into an outlet.
import { Outlet } from 'react-router-dom';
const Project = () => {
return (
<div className='content-wrapper'>
<Scroll />
<div className='project-intro'>
<Outlet /> // <-- render nested routes here
</div>
<ProjectContent />
<ProjectGrid />
<Contact />
</div>
);
};
App
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/project" element={<Project />}>
<Route path="projectOne" element={<ProjectOne />} />
<Route path="projectTwo" element={<ProjectTwo />} />
</Route>
</Routes>
</Router>
will try to be brief.
Dashboard component is rendering, but while hitting localhost:3000/dashboard/shipments nothing is rendering.
Not versed in the react, not sure if render={({ location })(Line 1) is causing problem.
Tried placing components/simply h4 tag in Route (Line2) but nothing working.
Necessary imports are done.
App.js
const pages = [
{
pageLink: '/dashboard',
view: Dashboard,
displayName: 'Dashboard',
showInNavbar: true,
exact: false
},....more routes.
return(
<Router>
<Route render={({ location }) => (//---------->Line 1
<React.Fragment>
<Navbar />
<Switch location={location}>
{pages.map((page, index) => {
return (
<Route
exact={page.exact}
path={page.pageLink}
component={page.view}
key={index}
/>
)
})}
<Redirect to='/' />
</Switch>
</React.Fragment>
)}
/>
</Router>
)
dashboard.js
export default function Dashboard() {
const authedUser = useSelector(state => state.authedUser);
let { path } = useRouteMatch();
if (!authedUser.loggedIn) return <Redirect to='/login' />
return (
<React.Fragment>
<section id='dashboard-component'>
<Sidebar />
<Switch>
<Route exact path={path}>
<h2>Dashboard</h2>
</Route>
<Route exact path={`/${path}/shipments`}><h4>sdsd</h4></Route>//------>Line 2
</Switch>
</section>
</React.Fragment>
)
}
You have a extra / at the start of your nested Route
<Route exact path={`/${path}/shipments`}><h4>sdsd</h4></Route>
Now path already return /dashboard. Writing path={`/${path}/shipments`} would make the route path as path={'//dashboard/shipments'}
You need to specify your child route like
<Route exact path={`${path}/shipments`}><h4>sdsd</h4></Route>
Working demo
In my app, I'd like to match all routs that end with #something.
/map#login
/info#login
and
/map#register
/map/one#register
/info#register
/info/two#register
So I can show component as popup on top of the content. How this can be done?
I found a solution for this case. It was inspired from this question in stackOverflow. Using HashRoute wrapper for Route and showing component based on location.hash.
const HashRoute = ({ component: Component, hash, ...routeProps }) => (
<Route
{...routeProps}
component={({ location, ...props }) =>
location.hash === hash && <Component {...props} />
}
/>
);
export default class App extends Component {
constructor() {
super();
}
render() {
return (
<div className='App'>
<Router history={history}>
<HashRoute hash='#login'component={Login} />
<HashRoute hash='#register' component={Register} />
<Switch>
<Route exact path='/map' component={Map} />
<Route exact path='/info' component={Info} />
</Switch>
</Router>
</div>
);
}
}
Updating/improving from the other answer here. It would better to not use the component prop as it won't create new instance of the routed component each time the Route is rendered for any reason. The custom HashRoute component should return valid JSX, either a Route component or null.
Example:
const HashRoute = ({ hash, ...routeProps }) => {
const location = useLocation();
return location.hash === hash
? <Route {...routeProps} />
: null
};
...
<Router>
<HashRoute hash='#login' component={Login} />
<HashRoute
hash='#register'
render={props => <Register {...props} otherProp />}
/>
<HashRoute hash='#something'>
<Register otherProp />
</HashRoute>
<Switch>
<Route path='/map' component={Map} />
<Route path='/info' component={Info} />
</Switch>
</Router>
My app is currently separated into 3 parts:
Frontend
Administration
Error
Frontend, Administration and the Error component have their own styling.
The Frontend and Administration component are also have their own Switch component to navigate through them.
The problem I am facing is that I can't hit the NoMatch path without a Redirect component. But when I do this I lose the wrong path in the browser URL.
Is there a chance when the inner Switch component has no matching route that it keeps searching in its parent Switch component?
Then I would be able to hit the NoMatch route and also keep the wrong path in the URL.
Edit: I updated my answer below with the final solution that is working like intended.
const Frontend = (props) => {
const { match } = props;
return (<div>
<h1>Frontend</h1>
<p><Link to={match.path}>Home</Link></p>
<p><Link to={`${match.path}users`}>Users</Link></p>
<p><Link to="/admin">Admin</Link></p>
<p><Link to={`${match.path}not-found-page`}>404</Link></p>
<hr />
<Switch>
<Route exact path={match.path} component={Home} />
<Route path={`${match.path}users`} component={Users} />
{
// Workaround
}
<Redirect to="/error" />
</Switch>
</div>);
};
const Admin = (props) => {
const { match } = props;
return (<div>
<h1>Admin</h1>
<p><Link to={match.path}>Dashboard</Link></p>
<p><Link to={`${match.path}/users`}>Edit Users</Link></p>
<p><Link to="/">Frontend</Link></p>
<p><Link to={`${match.path}/not-found-page`}>404</Link></p>
<hr />
<Switch>
<Route exact path={match.path} component={Home} />
<Route path={`${match.path}/users`} component={Users} />
{
// Workaround
}
<Redirect to="/error" />
</Switch>
</div>);
};
const ErrorPage = () =>
<div>
<h1>404 not found</h1>
<p><Link to="/">Home</Link></p>
</div>;
const App = () => (
<div>
<AddressBar />
<Switch>
<Route path="/error" component={ErrorPage} />
<Route path="/admin" component={Admin} />
<Route path="/" component={Frontend} />
{
// this should render the error page
// instead of redirecting to /error
}
<Route component={ErrorPage} />
</Switch>
</div>
);
Here is the final solution for this kind of requirement.
To make it work we use the location's state property. On the redirect in the inner routes we set the state to error: true.
On the GlobalErrorSwitch we check the state and render the error component.
import React, { Component } from 'react';
import { Switch, Route, Redirect, Link } from 'react-router-dom';
const Home = () => <div><h1>Home</h1></div>;
const User = () => <div><h1>User</h1></div>;
const Error = () => <div><h1>Error</h1></div>
const Frontend = props => {
console.log('Frontend');
return (
<div>
<h2>Frontend</h2>
<p><Link to="/">Root</Link></p>
<p><Link to="/user">User</Link></p>
<p><Link to="/admin">Backend</Link></p>
<p><Link to="/the-route-is-swiggity-swoute">Swiggity swooty</Link></p>
<Switch>
<Route exact path='/' component={Home}/>
<Route path='/user' component={User}/>
<Redirect to={{
state: { error: true }
}} />
</Switch>
<footer>Bottom</footer>
</div>
);
}
const Backend = props => {
console.log('Backend');
return (
<div>
<h2>Backend</h2>
<p><Link to="/admin">Root</Link></p>
<p><Link to="/admin/user">User</Link></p>
<p><Link to="/">Frontend</Link></p>
<p><Link to="/admin/the-route-is-swiggity-swoute">Swiggity swooty</Link></p>
<Switch>
<Route exact path='/admin' component={Home}/>
<Route path='/admin/user' component={User}/>
<Redirect to={{
state: { error: true }
}} />
</Switch>
<footer>Bottom</footer>
</div>
);
}
class GlobalErrorSwitch extends Component {
previousLocation = this.props.location
componentWillUpdate(nextProps) {
const { location } = this.props;
if (nextProps.history.action !== 'POP'
&& (!location.state || !location.state.error)) {
this.previousLocation = this.props.location
};
}
render() {
const { location } = this.props;
const isError = !!(
location.state &&
location.state.error &&
this.previousLocation !== location // not initial render
)
return (
<div>
{
isError
? <Route component={Error} />
: <Switch location={isError ? this.previousLocation : location}>
<Route path="/admin" component={Backend} />
<Route path="/" component={Frontend} />
</Switch>}
</div>
)
}
}
class App extends Component {
render() {
return <Route component={GlobalErrorSwitch} />
}
}
export default App;
All child component routes are wrapped in the <Switch> the parent (the switch inside the app component) you don't actually the switch in the child components.
Simply remove child switch.component and let the 404 in the <App <Switch> catch any missing.