Skip to content

Commit 4b79b96

Browse files
author
Brandon John Lewis
committed
add support for an array of paths in <Route> and matchPath
1 parent 0aef848 commit 4b79b96

File tree

6 files changed

+146
-23
lines changed

6 files changed

+146
-23
lines changed

packages/react-router/docs/api/Route.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,14 +133,18 @@ This could also be useful for animations:
133133

134134
**Warning:** Both `<Route component>` and `<Route render>` take precedence over `<Route children>` so don't use more than one in the same `<Route>`.
135135

136-
## path: string
136+
## path: string | string[]
137137

138-
Any valid URL path that [`path-to-regexp@^1.7.0`](https://github.com/pillarjs/path-to-regexp/tree/v1.7.0) understands.
138+
Any valid URL path or array of paths that [`path-to-regexp@^1.7.0`](https://github.com/pillarjs/path-to-regexp/tree/v1.7.0) understands.
139139

140140
```jsx
141141
<Route path="/users/:id" component={User} />
142142
```
143143

144+
```jsx
145+
<Route path={["/users/:id", "/profile/:id"]} component={User} />
146+
```
147+
144148
Routes without a `path` _always_ match.
145149

146150
## exact: bool

packages/react-router/docs/api/matchPath.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ to the matching props `Route` accepts:
2424

2525
```js
2626
{
27-
path, // like /users/:id
27+
path, // like /users/:id; either a single string or an array of strings
2828
strict, // optional, defaults to false
2929
exact; // optional, defaults to false
3030
}

packages/react-router/modules/Route.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,10 @@ if (__DEV__) {
130130
component: PropTypes.func,
131131
exact: PropTypes.bool,
132132
location: PropTypes.object,
133-
path: PropTypes.string,
133+
path: PropTypes.oneOfType([
134+
PropTypes.string,
135+
PropTypes.arrayOf(PropTypes.string)
136+
]),
134137
render: PropTypes.func,
135138
sensitive: PropTypes.bool,
136139
strict: PropTypes.bool

packages/react-router/modules/__tests__/Route-test.js

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,83 @@ describe("A <Route>", () => {
9898
});
9999
});
100100

101+
describe("with an array of paths", () => {
102+
it("matches the first provided path", () => {
103+
const node = document.createElement("div");
104+
ReactDOM.render(
105+
<MemoryRouter initialEntries={["/hello"]}>
106+
<Route
107+
path={["/hello", "/world"]}
108+
render={() => <div>Hello World</div>}
109+
/>
110+
</MemoryRouter>,
111+
node
112+
);
113+
114+
expect(node.innerHTML).toContain("Hello World");
115+
});
116+
117+
it("matches other provided paths", () => {
118+
const node = document.createElement("div");
119+
ReactDOM.render(
120+
<MemoryRouter initialEntries={["/other", "/world"]} initialIndex={1}>
121+
<Route
122+
path={["/hello", "/world"]}
123+
render={() => <div>Hello World</div>}
124+
/>
125+
</MemoryRouter>,
126+
node
127+
);
128+
129+
expect(node.innerHTML).toContain("Hello World");
130+
});
131+
132+
it("provides the matched path as a string", () => {
133+
const node = document.createElement("div");
134+
ReactDOM.render(
135+
<MemoryRouter initialEntries={["/other", "/world"]} initialIndex={1}>
136+
<Route
137+
path={["/hello", "/world"]}
138+
render={({ match }) => <div>{match.path}</div>}
139+
/>
140+
</MemoryRouter>,
141+
node
142+
);
143+
144+
expect(node.innerHTML).toContain("/world");
145+
});
146+
147+
it("doesn't remount when moving from one matching path to another", () => {
148+
const node = document.createElement("div");
149+
const history = createHistory();
150+
const mount = jest.fn();
151+
class MatchedRoute extends React.Component {
152+
componentWillMount() {
153+
mount();
154+
}
155+
156+
render() {
157+
return <div>Hello World</div>;
158+
}
159+
}
160+
history.push("/hello");
161+
ReactDOM.render(
162+
<Router history={history}>
163+
<Route path={["/hello", "/world"]} component={MatchedRoute} />
164+
</Router>,
165+
node
166+
);
167+
168+
expect(mount).toHaveBeenCalledTimes(1);
169+
expect(node.innerHTML).toContain("Hello World");
170+
171+
history.push("/world/somewhere/else");
172+
173+
expect(mount).toHaveBeenCalledTimes(1);
174+
expect(node.innerHTML).toContain("Hello World");
175+
});
176+
});
177+
101178
describe("with a unicode path", () => {
102179
it("is able to match", () => {
103180
ReactDOM.render(

packages/react-router/modules/__tests__/matchPath-test.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,36 @@ describe("matchPath", () => {
3333
});
3434
});
3535

36+
describe("with an array of paths", () => {
37+
it('return the correct url at "/elsewhere"', () => {
38+
const path = ["/somewhere", "/elsewhere"];
39+
const pathname = "/elsewhere";
40+
const match = matchPath(pathname, { path });
41+
expect(match.url).toBe("/elsewhere");
42+
});
43+
44+
it('returns correct url at "/elsewhere/else"', () => {
45+
const path = ["/somewhere", "/elsewhere"];
46+
const pathname = "/elsewhere/else";
47+
const match = matchPath(pathname, { path });
48+
expect(match.url).toBe("/elsewhere");
49+
});
50+
51+
it('returns correct url at "/elsewhere/else" with path "/" in array', () => {
52+
const path = ["/somewhere", "/"];
53+
const pathname = "/elsewhere/else";
54+
const match = matchPath(pathname, { path });
55+
expect(match.url).toBe("/");
56+
});
57+
58+
it('returns correct url at "/somewhere" with path "/" in array', () => {
59+
const path = ["/somewhere", "/"];
60+
const pathname = "/somewhere";
61+
const match = matchPath(pathname, { path });
62+
expect(match.url).toBe("/somewhere");
63+
});
64+
});
65+
3666
describe("with sensitive path", () => {
3767
it("returns non-sensitive url", () => {
3868
const options = {

packages/react-router/modules/matchPath.js

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -30,25 +30,34 @@ function matchPath(pathname, options = {}) {
3030

3131
const { path, exact = false, strict = false, sensitive = false } = options;
3232

33-
const { regexp, keys } = compilePath(path, { end: exact, strict, sensitive });
34-
const match = regexp.exec(pathname);
35-
36-
if (!match) return null;
37-
38-
const [url, ...values] = match;
39-
const isExact = pathname === url;
40-
41-
if (exact && !isExact) return null;
42-
43-
return {
44-
path, // the path used to match
45-
url: path === "/" && url === "" ? "/" : url, // the matched portion of the URL
46-
isExact, // whether or not we matched exactly
47-
params: keys.reduce((memo, key, index) => {
48-
memo[key.name] = values[index];
49-
return memo;
50-
}, {})
51-
};
33+
const paths = [].concat(path);
34+
35+
return paths.reduce((matched, path) => {
36+
if (matched) return matched;
37+
const { regexp, keys } = compilePath(path, {
38+
end: exact,
39+
strict,
40+
sensitive
41+
});
42+
const match = regexp.exec(pathname);
43+
44+
if (!match) return null;
45+
46+
const [url, ...values] = match;
47+
const isExact = pathname === url;
48+
49+
if (exact && !isExact) return null;
50+
51+
return {
52+
path, // the path used to match
53+
url: path === "/" && url === "" ? "/" : url, // the matched portion of the URL
54+
isExact, // whether or not we matched exactly
55+
params: keys.reduce((memo, key, index) => {
56+
memo[key.name] = values[index];
57+
return memo;
58+
}, {})
59+
};
60+
}, null);
5261
}
5362

5463
export default matchPath;

0 commit comments

Comments
 (0)