Skip to content

Commit

Permalink
[react-router-dom] navlink, renderMatches & matchRoutes (#4425)
Browse files Browse the repository at this point in the history
* [react-router-dom] drop outdated support of Navlink props `activeClassName` & `activeStyle`

also improve type className & style then add tests

cf. https://reactrouter.com/en/main/upgrading/v5#remove-activeclassname-and-activestyle-props-from-navlink-

* [react-router-dom] Navlink prop `exact` renamed to `end`

cf. https://reactrouter.com/en/main/upgrading/v5#rename-navlink-exact-to-navlink-end

* [react-router-dom] Navlink's `isActive` prop has been removed

`isActive` has been removed between versions 5 & 6 without notice:

v5: https://v5.reactrouter.com/web/api/NavLink/isactive-func
latest: https://reactrouter.com/en/main/components/nav-link#navlink

* [react-router-dom] Navlink accepts render prop

cf. https://reactrouter.com/en/main/components/nav-link#children

* [react-router-dom] refactor `renderMatches` & `matchRoutes` types

improve types to make it more precise and more close to typescript definition

cf.
matchRoute https://github.com/remix-run/react-router/blob/d19c2c221c9f53b8273033f31b0ee94734719f47/packages/router/utils.ts#L374
renderRoutes https://github.com/remix-run/react-router/blob/d19c2c221c9f53b8273033f31b0ee94734719f47/packages/react-router/lib/components.tsx#L630
  • Loading branch information
iamdey committed Mar 16, 2023
1 parent 48815e6 commit 1ec13c5
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 23 deletions.
Expand Up @@ -24,13 +24,10 @@ declare module "react-router-dom" {

declare export var NavLink: React$ComponentType<{
+to: string | LocationShape,
+activeClassName?: string,
+className?: string,
+activeStyle?: { +[string]: mixed, ... },
+style?: { +[string]: mixed, ... },
+isActive?: (match: Match, location: Location) => boolean,
+children?: React$Node,
+exact?: boolean,
+className?: string | (props: {| isActive: boolean, isPending: boolean |}) => string | void,
+style?: { +[string]: mixed, ... } | (props: {| isActive: boolean, isPending: boolean|}) => { +[string]: mixed, ... } | void,
+children?: React$Node | ({| isActive: boolean, isPending: boolean |}) => React$Node,
+end?: boolean,
+strict?: boolean,
...
}>
Expand Down Expand Up @@ -464,28 +461,23 @@ declare module "react-router-dom" {

declare export type RouteObject = IndexRouteObject | NonIndexRouteObject;

declare export function createRoutesFromChildren(
children: React$Node,
): Array<RouteObject>;
declare export function createRoutesFromElements(elements: React$Node): RouteObject[]
declare export var createRoutesFromChildren: typeof createRoutesFromElements;

declare export type Params<Key: string> = {
+[key: Key]: string | void;
};

declare type RouteMatch<ParamKey: string> = {|
params: Params<ParamKey>,
pathname: string,
route: RouteObject,
|};
declare export type RouteMatch<ParamKey: string = string, RouteObjectType: RouteObject = RouteObject> = AgnosticRouteMatch<ParamKey, RouteObjectType>

declare export function matchRoutes(
declare export function matchRoutes<RouteObjectType: RouteObject = RouteObject>(
routes: Array<RouteObject>,
location: LocationShape | string,
basename?: string,
): Array<RouteMatch<string>> | null;
): Array<AgnosticRouteMatch<string, RouteObjectType>> | null;

declare export function renderMatches(
matches: Array<RouteMatch<string>> | null,
declare export function renderMatches<RouteObjectType = RouteObject>(
matches: Array<RouteMatch<string, RouteObjectType>> | null,
): React$Element<any> | null;

declare type PathPattern = {|
Expand Down
Expand Up @@ -6,6 +6,8 @@ import {
Link,
NavLink,
matchPath,
matchRoutes,
renderMatches,
withRouter,
Navigate,
Outlet,
Expand All @@ -21,6 +23,8 @@ import {
useMatches,
} from "react-router-dom";
import type {
AgnosticRouteMatch,
RouteObject,
Location,
ContextRouter,
Match,
Expand Down Expand Up @@ -122,13 +126,10 @@ describe("react-router-dom", () => {

<NavLink
to="/about"
activeClassName="active"
className="link"
activeStyle={{ color: "red" }}
style={{ color: "blue" }}
isActive={(match, location) => true}
strict
exact
end
>
About
</NavLink>;
Expand Down Expand Up @@ -164,6 +165,44 @@ describe("react-router-dom", () => {

// $FlowExpectedError[incompatible-type] - to prop must be a string or LocationShape
<NavLink to={[]} />;

// activeClassName, activeStyle, end, isActive have been dropped unfortunately props cannot be strict so no errors can be expected
<NavLink
to="/about"
activeClassName="active"
activeStyle={{ color: "red" }}
isActive={(match: any, location: any) => true}
end
>
About
</NavLink>;
});

it('supports enhanced className & style props', () => {
<NavLink
to="/about"
className={({ isActive, isPending}) =>
isPending ? "pending" : isActive ? "active" : undefined
}
style={({ isActive, isPending}) =>
isPending ? { color: "red" } : isActive ? { color: "blue" } : undefined
}
>
About
</NavLink>;

// $FlowExpectedError[incompatible-type]
<NavLink to="/about" className={{'invalid': ''}} />;
// $FlowExpectedError[incompatible-type]
<NavLink to="/about" style={3} />;
});

it('supports render prop as children', () => {
<NavLink to="/about">
{({ isActive, isPending }) => (
<span className={isActive ? "active" : ""}>Tasks</span>
)}
</NavLink>
});
});

Expand Down Expand Up @@ -198,6 +237,58 @@ describe("react-router-dom", () => {
});
});

describe('renderMatches', () => {
it('works', () => {
renderMatches([]);

renderMatches<RouteObject>([]);

const contentWithEmptyMatches: null|React$Element<any> = renderMatches([]);

const contentWithMatches: null|React$Element<any> = renderMatches([{
params: {},
pathname: '/',
pathnameBase: '',
route: {
index: false,
children: [{
index: true,
}],
},
}]);
});

it('raises', () => {
// $FlowExpectedError[incompatible-call]
renderMatches(5);

// $FlowExpectedError[incompatible-type]
const contentWithEmptyMatches: number = renderMatches([]);
});
});

describe('matchRoutes', () => {
it('works', () => {
matchRoutes([], '/');

matchRoutes<RouteObject>([], '/');

const contentWithEmptyMatches: Array<AgnosticRouteMatch<string, RouteObject>> | null = matchRoutes([], '/');

const contentWithMatches: Array<AgnosticRouteMatch<string, RouteObject>> | null = matchRoutes([{
id: 'bar',
path: 'bar',
index: false,
children: [],
}], '/');
});

it('raises an error with invalid arguments', () => {
// $FlowExpectedError[incompatible-call]
matchRoutes(5, '/');
});
});

describe("withRouter", () => {
type Props = {
history: RouterHistory,
Expand Down

0 comments on commit 1ec13c5

Please sign in to comment.