Skip to content

Commit b3c7d99

Browse files
authored
NavLink: Add support for className and style props as functions (#7983)
1 parent cc61615 commit b3c7d99

File tree

3 files changed

+124
-8
lines changed

3 files changed

+124
-8
lines changed

packages/react-router-dom/docs/api/NavLink.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,23 @@ A special version of the [`<Link>`](Link.md) that will add styling attributes to
66
<NavLink to="/about">About</NavLink>
77
```
88

9+
## className: string | func
10+
11+
`className` can either be a string or a function that returns a string. If the function `className` is used, the link's `active` state is passed as a parameter. This is helpful if you want to exclusively apply a `className` to an inactive link.
12+
13+
```jsx
14+
<NavLink
15+
to="/faq"
16+
className={isActive =>
17+
"nav-link" + (!isActive ? " unselected" : "")
18+
}
19+
>
20+
FAQs
21+
</NavLink>
22+
```
23+
24+
In React Router v6, `activeClassName` will be removed and you should use the function `className` to apply classnames to either active or inactive `NavLink` components.
25+
926
## activeClassName: string
1027

1128
The class to give the element when it is active. The default given class is `active`. This will be joined with the `className` prop.
@@ -16,6 +33,23 @@ The class to give the element when it is active. The default given class is `act
1633
</NavLink>
1734
```
1835

36+
## style: object | func
37+
38+
`style` can either be a `React.CSSProperties` object or a function that returns a style object. If the function `style` is used, the link's `active` state is passed as a parameter.
39+
40+
```jsx
41+
<NavLink
42+
to="/faq"
43+
style={isActive => ({
44+
color: isActive ? "green" : "blue"
45+
})}
46+
>
47+
FAQs
48+
</NavLink>
49+
```
50+
51+
In React Router v6, `activeStyle` will be removed and you should use the function `style` to apply inline styles to either active or inactive `NavLink` components.
52+
1953
## activeStyle: object
2054

2155
The styles to apply to the element when it is active.

packages/react-router-dom/modules/NavLink.js

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ const NavLink = forwardRef(
2626
(
2727
{
2828
"aria-current": ariaCurrent = "page",
29-
activeClassName = "active",
30-
activeStyle,
29+
activeClassName = "active", // TODO: deprecate
30+
activeStyle, // TODO: deprecate
3131
className: classNameProp,
3232
exact,
3333
isActive: isActiveProp,
@@ -68,10 +68,18 @@ const NavLink = forwardRef(
6868
? isActiveProp(match, currentLocation)
6969
: match);
7070

71-
const className = isActive
72-
? joinClassnames(classNameProp, activeClassName)
73-
: classNameProp;
74-
const style = isActive ? { ...styleProp, ...activeStyle } : styleProp;
71+
let className =
72+
typeof classNameProp === "function"
73+
? classNameProp(isActive)
74+
: classNameProp;
75+
76+
let style =
77+
typeof styleProp === "function" ? styleProp(isActive) : styleProp;
78+
79+
if (isActive) {
80+
className = joinClassnames(className, activeClassName);
81+
style = { ...style, ...activeStyle };
82+
}
7583

7684
const props = {
7785
"aria-current": (isActive && ariaCurrent) || null,
@@ -113,13 +121,13 @@ if (__DEV__) {
113121
"aria-current": ariaCurrentType,
114122
activeClassName: PropTypes.string,
115123
activeStyle: PropTypes.object,
116-
className: PropTypes.string,
124+
className: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
117125
exact: PropTypes.bool,
118126
isActive: PropTypes.func,
119127
location: PropTypes.object,
120128
sensitive: PropTypes.bool,
121129
strict: PropTypes.bool,
122-
style: PropTypes.object
130+
style: PropTypes.oneOfType([PropTypes.object, PropTypes.func])
123131
};
124132
}
125133

packages/react-router-dom/modules/__tests__/NavLink-test.js

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,43 @@ describe("A <NavLink>", () => {
9393
expect(a.style.color).toBe(activeStyle.color);
9494
});
9595

96+
it("applies its className when provided as a function", () => {
97+
renderStrict(
98+
<MemoryRouter initialEntries={["/pizza"]}>
99+
<NavLink
100+
to="/pizza"
101+
className={isActive => (isActive ? "active-pizza" : "chill-pizza")}
102+
>
103+
Pizza!
104+
</NavLink>
105+
</MemoryRouter>,
106+
node
107+
);
108+
109+
const a = node.querySelector("a");
110+
expect(a.className).toContain("active-pizza");
111+
});
112+
113+
it("applies its style when provided as a function", () => {
114+
const defaultStyle = { color: "black" };
115+
const activeStyle = { color: "red" };
116+
117+
renderStrict(
118+
<MemoryRouter initialEntries={["/pizza"]}>
119+
<NavLink
120+
to="/pizza"
121+
style={isActive => (isActive ? activeStyle : defaultStyle)}
122+
>
123+
Pizza!
124+
</NavLink>
125+
</MemoryRouter>,
126+
node
127+
);
128+
129+
const a = node.querySelector("a");
130+
expect(a.style.color).toBe(activeStyle.color);
131+
});
132+
96133
it("applies the default aria-current", () => {
97134
renderStrict(
98135
<MemoryRouter initialEntries={["/pizza"]}>
@@ -217,6 +254,43 @@ describe("A <NavLink>", () => {
217254
expect(a.style.color).toBe(defaultStyle.color);
218255
});
219256

257+
it("applies its className when provided as a function", () => {
258+
renderStrict(
259+
<MemoryRouter initialEntries={["/pizza"]}>
260+
<NavLink
261+
to="/salad"
262+
className={isActive => (isActive ? "active-salad" : "chill-salad")}
263+
>
264+
Salad?
265+
</NavLink>
266+
</MemoryRouter>,
267+
node
268+
);
269+
270+
const a = node.querySelector("a");
271+
expect(a.className).toContain("chill-salad");
272+
});
273+
274+
it("applies its style when provided as a function", () => {
275+
const defaultStyle = { color: "black" };
276+
const activeStyle = { color: "red" };
277+
278+
renderStrict(
279+
<MemoryRouter initialEntries={["/pizza"]}>
280+
<NavLink
281+
to="/salad"
282+
style={isActive => (isActive ? activeStyle : defaultStyle)}
283+
>
284+
Salad?
285+
</NavLink>
286+
</MemoryRouter>,
287+
node
288+
);
289+
290+
const a = node.querySelector("a");
291+
expect(a.style.color).toBe(defaultStyle.color);
292+
});
293+
220294
it("does not apply an aria-current value if no override value is given", () => {
221295
renderStrict(
222296
<MemoryRouter initialEntries={["/pizza"]}>

0 commit comments

Comments
 (0)