const Root = () => (
<NavLink to="user">Go to /user</NavLink>
<NavLink to="user-preferences">Go to /user-preferences</NavLink>
Navigate to /user-preferences
path
Code Sandbox
Expected Behavior
The link to /user-preferences should have the active class and the link to /user should not have the active class. This is the behavior in [email protected]
Actual Behavior
Both links have the active class.
What happens if you add end
prop to your <NavLink>
? It looks like it's matching on partial URL: basically isActive = path.startsWith('/user')
.
Adding end
does sort of workaround it, but it means that the child routes of /user
no longer match. This worked without end
in 6.3. My expectation is that isActive = useMatch('/user') != null
.
There's a test that checks that partial segments do not apply the active
class. If I change the /children
route to /child-ren
the test fails in 6.4.0 and passes in 6.3.0.
[Bug]: NavLink is active when useMatch is false (react-router-dom v6.4)
[Bug]: NavLink is active for partial segment matches (react-router-dom v6.4)
Sep 16, 2022
Two updates, first I found why the useMatch()
hook has a different result than the component. useMatch('/user')
will default the unset end
parameter to true
, while useMatch({pathname: '/user'})
(what NavLink will effectively call) will default end
to false
My other finding is that I think the regression comes from this part of the regular expression defined in compilePath
// Otherwise, match a word boundary or a proceeding /. The word boundary restricts
// parent routes to matching only their own words and nothing more, e.g. parent
// route "/home" should not match "/home2".
// Additionally, allow paths starting with `.`, `-`, `~`, and url-encoded entities,
// but do not consume the character in the matched path so they can match against
// nested paths.
"(?:(?=[@.~-]|%[0-9A-F]{2})|\\b|\\/|$)";
Its treating -
as a word boundary and therefore like as if /user-preferences
is a child route of /user
. It feels like the part that says allow paths starting with special characters, should mean only match if the path was something like /user/-preferences
.
NavLink in 6.3 didn't use useMatch
and this regular expression.
Hello mate,
First, in this case, I suggest splitting the pathname "user-preferences" to "user/preferences" but another solution is to make a custom NavLink component.
Like this:
const CustomNavLink = ({ className, children, to }) => {
const { pathname } = useLocation();
const match = to === pathname.split('/').join('');
return (
<NavLink to={to} style={{color: match ? 'red': 'black'}} >
{children}
</NavLink>
I looked into this briefly and I think this may be a previously undiscovered bug in matchPath that's now exposed more broadly since we changed NavLink to use it internally. We did this to better align its approach with our recommended approach for custom NavLink behavior in https://github.com/remix-run/react-router/blob/main/examples/custom-link/src/App.tsx.
Going to chat with @mjackson to confirm since he wrote most of the original approach in matchPath.