Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow NavLink to receive a children function #8164

Merged
merged 8 commits into from
Dec 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions contributors.yml
Expand Up @@ -11,3 +11,4 @@
- shivamsinghchahar
- timdorr
- turansky
- sergiodxa
27 changes: 23 additions & 4 deletions docs/api.md
Expand Up @@ -345,8 +345,14 @@ declare function NavLink(
): React.ReactElement;

interface NavLinkProps
extends Omit<LinkProps, "className" | "style"> {
extends Omit<
LinkProps,
"className" | "style" | "children"
> {
caseSensitive?: boolean;
children?:
| React.ReactNode
| ((props: { isActive: boolean }) => React.ReactNode);
className?:
| string
| ((props: { isActive: boolean }) => string);
Expand All @@ -363,7 +369,7 @@ interface NavLinkProps

A `<NavLink>` is a special kind of [`<Link>`](#link) that knows whether or not it is "active". This is useful when building a navigation menu such as a breadcrumb or a set of tabs where you'd like to show which of them is currently selected. It also provides useful context for assistive technology like screen readers.

By default, an `active` class is added to a `<NavLink>` component when it is active. This provides the same simple styling mechanism for most users who are upgrading from v5. One difference as of `v6.0.0-beta.3` is that `activeClassName` and `activeStyle` have been removed from `NavLinkProps`. Instead, you can pass a function to either `style` or `className` that will allow you to customize the inline styling or the class string based on the component's active state.
By default, an `active` class is added to a `<NavLink>` component when it is active. This provides the same simple styling mechanism for most users who are upgrading from v5. One difference as of `v6.0.0-beta.3` is that `activeClassName` and `activeStyle` have been removed from `NavLinkProps`. Instead, you can pass a function to either `style` or `className` that will allow you to customize the inline styling or the class string based on the component's active state. you can also pass a function as children to customize the content of the `<NavLink>` component based on their active state, specially useful to change styles on internal elements.

```tsx
import * as React from "react";
Expand All @@ -376,6 +382,8 @@ function NavList() {
textDecoration: "underline"
};

let activeClassName = "underline"

return (
<nav>
<ul>
Expand All @@ -392,13 +400,24 @@ function NavList() {
<li>
<NavLink
to="tasks"
style={({ isActive }) =>
isActive ? activeStyle : undefined
className={({ isActive }) =>
isActive ? activeClassName : undefined
Comment on lines -395 to +404
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed this example to use className instead of style, so the API docs of NavLink now can have one example of style, className and children as functions.

}
>
Tasks
</NavLink>
</li>
<li>
<NavLink
to="tasks"
>
{({ isActive }) => (
<span className={isActive ? activeClassName : undefined}>
Tasks
</span>
))}
</NavLink>
</li>
</ul>
</nav>
);
Expand Down
42 changes: 42 additions & 0 deletions packages/react-router-dom/__tests__/nav-link-active-test.tsx
Expand Up @@ -23,6 +23,27 @@ describe("NavLink", () => {

expect(anchor.props.className).not.toMatch("active");
});

it("does not change the content inside the <a>", () => {
let renderer = TestRenderer.create(
<MemoryRouter initialEntries={["/home"]}>
<Routes>
<Route
path="/home"
element={
<NavLink to="somewhere-else">
{({ isActive }) => (isActive ? "Current" : "Somewhere else")}
</NavLink>
}
/>
</Routes>
</MemoryRouter>
);

let anchor = renderer.root.findByType("a");

expect(anchor.children[0]).toMatch("Somewhere else");
});
});

describe("when it matches to the end", () => {
Expand Down Expand Up @@ -102,6 +123,27 @@ describe("NavLink", () => {

expect(anchor.props.style).toMatchObject({ textTransform: "uppercase" });
});

it("applies its children correctly when provided as a function", () => {
let renderer = TestRenderer.create(
<MemoryRouter initialEntries={["/home"]}>
<Routes>
<Route
path="/home"
element={
<NavLink to=".">
{({ isActive }) => (isActive ? "Home (current)" : "Home")}
</NavLink>
}
/>
</Routes>
</MemoryRouter>
);

let anchor = renderer.root.findByType("a");

expect(anchor.children[0]).toMatch("Home (current)");
});
});

describe("when it matches a partial URL segment", () => {
Expand Down
11 changes: 9 additions & 2 deletions packages/react-router-dom/index.tsx
Expand Up @@ -275,7 +275,11 @@ if (__DEV__) {
Link.displayName = "Link";
}

export interface NavLinkProps extends Omit<LinkProps, "className" | "style"> {
export interface NavLinkProps
extends Omit<LinkProps, "className" | "style" | "children"> {
children:
| React.ReactNode
| ((props: { isActive: boolean }) => React.ReactNode);
caseSensitive?: boolean;
className?: string | ((props: { isActive: boolean }) => string);
end?: boolean;
Expand All @@ -296,6 +300,7 @@ export const NavLink = React.forwardRef<HTMLAnchorElement, NavLinkProps>(
end = false,
style: styleProp,
to,
children,
...rest
},
ref
Expand Down Expand Up @@ -343,7 +348,9 @@ export const NavLink = React.forwardRef<HTMLAnchorElement, NavLinkProps>(
ref={ref}
style={style}
to={to}
/>
>
{typeof children === "function" ? children({ isActive }) : children}
</Link>
);
}
);
Expand Down