Reputation: 5159
I'm using React Router v4 and I have a case where on my navigation links, I want to enable the active
className to the NavLink
parent element, not the NavLink
itself.
Is there a way to access the path (match
) even though I'm not inside the Switch
element?
Or do I have to keep state? Because I'm feeling it's kinda missing the idea of router.
Here's my example, I want to apply the active
className to li
element not NavLink
:
const {
HashRouter,
Switch,
Route,
Link,
NavLink,
} = ReactRouterDOM
const About = () => (
<article>
My name is Moshe and I'm learning React and React Router v4.
</article>
);
const Page = () => (
<Switch>
<Route exact path='/' render={() => <h1>Welcome!</h1>} />
<Route path='/about' component={About}/>
</Switch>
);
const Nav = () => (
<nav>
<ul>
<li><NavLink exact to="/">Home</NavLink></li>
<li><NavLink to="/about">About</NavLink></li>
</ul>
</nav>
);
class App extends React.Component {
render() {
return (
<div>
<Nav />
<Page />
</div>
);
}
}
ReactDOM.render((
<HashRouter>
<App />
</HashRouter>),
document.querySelector("#app"));
https://codepen.io/moshem/pen/ypzmQX
Upvotes: 25
Views: 13827
Reputation: 562
If you abandon the NavLink
components altogether, you can create your own components that emulate the "activeness" of a NavLink
by using useHistory()
and useLocation()
from react-router-dom
.
const routeItems = [
{ route: '/route1', text: 'Route 1' },
{ route: '/route2', text: 'Route 2' },
];
<Router>
<NavBar routeItems={routeItems} />
</Router>
In NavBar.js
, we just need to check to see if the current active route is the same as the route for any individual item on the
import { useHistory, useLocation } from 'react-router-dom';
const NavBar = (props) => {
const { routeItems } = props;
const history = useHistory();
const location = useLocation();
const navItems = routeItems.map((navItem) => {
return (
<div style={{
backgroundColor: navItem.route === location.pathname ? '#ADD8E6' : '',
}}
onClick={() => {
history.push(navItem.route);
}}
>
{navItem.text}
</div>
);
});
return (navItems);
};
export default NavBar;
Upvotes: 1
Reputation: 1242
I found simpler solution for my case I have nested items but I know the base of each nest
for example the base of nest is /customer
it contains items like so
/customer/list
, /customer/roles
...
So did put some logic in isActive
prop in the parent NavLink
code with explanation down :
<NavLink
to={item.route}
activeClassName={classes.activeItem}
onClick={e => handleItemClick(e, key)}
isActive={(match, location) => {
// remove last part of path ( admin/customer/list becomes admin/customer for example )
const pathWithoutLastPart = location.pathname.slice(0, location.pathname.lastIndexOf("/"));
// if current parent is matched and doesn't contain childs activate it
if (item.items.length === 0 && match) {
return true;
}
// if sliced path matches parent path
in case of customer item it becomes true ( admin/customer === admin/customer )
else if (pathWithoutLastPart === item.route) {
return true;
}
// else inactive item
else {
return false;
}
}}
>
...
</NavLink>
Now parent active with his child
Upvotes: 0
Reputation: 301
Can be achived with Route component
<ul>
<Route path="/about">
{({ match }) => <li className={match ? 'active' : undefined}><Link to="/about">About</Link></li>
</Route>
</ul>
Upvotes: 3
Reputation: 2781
It doesn't seem like it is very easy to achieve. I used withRouter
HOC described in react router docs. It gives access to { match, location, history }
from props
inside components located outside of Routes
s. In the example I wrapped Nav
component to get location
and its pathname
. Here is the example code:
class Nav extends React.Component {
getNavLinkClass = (path) => {
return this.props.location.pathname === path ? 'active' : '';
}
render() {
return (
<nav>
<ul>
<li className={this.getNavLinkClass("/")}><NavLink exact to="/">Home</NavLink></li>
<li className={this.getNavLinkClass("/about")}><NavLink to="/about">About</NavLink></li>
</ul>
</nav>
)};
}
Nav = withRouter(Nav);
You will probably have to take care of params
in your routes (if you have any), to match properly. But you still have to match for each path you have in your NavLink
, which might not be pretty code. But the idea is that when the route is changed, Nav
is rerendered and correct li
is highlighted.
Here is a working example on codesandbox.
Upvotes: 24