Skip to content

Commit d6224d6

Browse files
committedSep 19, 2019
Add some hooks for React 16.8 users
- useMatch - useParams - useLocation - useHistory
1 parent 19835fb commit d6224d6

File tree

8 files changed

+285
-23
lines changed

8 files changed

+285
-23
lines changed
 

Diff for: ‎packages/react-router-dom/.size-snapshot.json

+12-12
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
11
{
22
"esm/react-router-dom.js": {
3-
"bundled": 8797,
4-
"minified": 5223,
5-
"gzipped": 1682,
3+
"bundled": 9444,
4+
"minified": 5645,
5+
"gzipped": 1786,
66
"treeshaked": {
77
"rollup": {
8-
"code": 379,
9-
"import_statements": 355
8+
"code": 2272,
9+
"import_statements": 432
1010
},
1111
"webpack": {
12-
"code": 1612
12+
"code": 3572
1313
}
1414
}
1515
},
1616
"umd/react-router-dom.js": {
17-
"bundled": 129625,
18-
"minified": 46107,
19-
"gzipped": 14073
17+
"bundled": 131334,
18+
"minified": 46991,
19+
"gzipped": 14275
2020
},
2121
"umd/react-router-dom.min.js": {
22-
"bundled": 86293,
23-
"minified": 29465,
24-
"gzipped": 9792
22+
"bundled": 87085,
23+
"minified": 29827,
24+
"gzipped": 9885
2525
}
2626
}

Diff for: ‎packages/react-router/.size-snapshot.json

+11-11
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
11
{
22
"esm/react-router.js": {
3-
"bundled": 23515,
4-
"minified": 13336,
5-
"gzipped": 3696,
3+
"bundled": 24890,
4+
"minified": 14513,
5+
"gzipped": 3839,
66
"treeshaked": {
77
"rollup": {
8-
"code": 2376,
8+
"code": 2389,
99
"import_statements": 470
1010
},
1111
"webpack": {
12-
"code": 3743
12+
"code": 3758
1313
}
1414
}
1515
},
1616
"umd/react-router.js": {
17-
"bundled": 102011,
18-
"minified": 36085,
19-
"gzipped": 11534
17+
"bundled": 103056,
18+
"minified": 36690,
19+
"gzipped": 11666
2020
},
2121
"umd/react-router.min.js": {
22-
"bundled": 62277,
23-
"minified": 21931,
24-
"gzipped": 7707
22+
"bundled": 62526,
23+
"minified": 22088,
24+
"gzipped": 7748
2525
}
2626
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import React from "react";
2+
import ReactDOM from "react-dom";
3+
import { MemoryRouter, Route, useHistory } from "react-router";
4+
5+
import renderStrict from "./utils/renderStrict.js";
6+
7+
describe("useHistory", () => {
8+
const node = document.createElement("div");
9+
10+
afterEach(() => {
11+
ReactDOM.unmountComponentAtNode(node);
12+
});
13+
14+
it("returns the history object", () => {
15+
let history;
16+
17+
function HomePage() {
18+
history = useHistory();
19+
return null;
20+
}
21+
22+
renderStrict(
23+
<MemoryRouter initialEntries={["/home"]}>
24+
<Route path="/home">
25+
<HomePage />
26+
</Route>
27+
</MemoryRouter>,
28+
node
29+
);
30+
31+
expect(typeof history).toBe("object");
32+
expect(typeof history.push).toBe("function");
33+
});
34+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import React from "react";
2+
import ReactDOM from "react-dom";
3+
import { MemoryRouter, Route, useLocation } from "react-router";
4+
5+
import renderStrict from "./utils/renderStrict.js";
6+
7+
describe("useLocation", () => {
8+
const node = document.createElement("div");
9+
10+
afterEach(() => {
11+
ReactDOM.unmountComponentAtNode(node);
12+
});
13+
14+
it("returns the current location object", () => {
15+
let location;
16+
17+
function HomePage() {
18+
location = useLocation();
19+
return null;
20+
}
21+
22+
renderStrict(
23+
<MemoryRouter initialEntries={["/home"]}>
24+
<Route path="/home">
25+
<HomePage />
26+
</Route>
27+
</MemoryRouter>,
28+
node
29+
);
30+
31+
expect(typeof location).toBe("object");
32+
expect(location).toMatchObject({
33+
pathname: "/home"
34+
});
35+
});
36+
});
+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import React from "react";
2+
import ReactDOM from "react-dom";
3+
import { MemoryRouter, Route, useMatch } from "react-router";
4+
5+
import renderStrict from "./utils/renderStrict.js";
6+
7+
describe("useMatch", () => {
8+
const node = document.createElement("div");
9+
10+
afterEach(() => {
11+
ReactDOM.unmountComponentAtNode(node);
12+
});
13+
14+
it("returns the match object", () => {
15+
let match;
16+
17+
function HomePage() {
18+
match = useMatch();
19+
return null;
20+
}
21+
22+
renderStrict(
23+
<MemoryRouter initialEntries={["/home"]}>
24+
<Route path="/home">
25+
<HomePage />
26+
</Route>
27+
</MemoryRouter>,
28+
node
29+
);
30+
31+
expect(typeof match).toBe("object");
32+
expect(match).toMatchObject({
33+
path: "/home",
34+
url: "/home",
35+
isExact: true
36+
});
37+
});
38+
});
+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import React from "react";
2+
import ReactDOM from "react-dom";
3+
import { MemoryRouter, Route, useMatch, useParams } from "react-router";
4+
5+
import renderStrict from "./utils/renderStrict.js";
6+
7+
describe("useParams", () => {
8+
const node = document.createElement("div");
9+
10+
afterEach(() => {
11+
ReactDOM.unmountComponentAtNode(node);
12+
});
13+
14+
describe("when the path has no params", () => {
15+
it("returns an empty hash", () => {
16+
let params;
17+
18+
function HomePage() {
19+
params = useParams();
20+
return null;
21+
}
22+
23+
renderStrict(
24+
<MemoryRouter initialEntries={["/home"]}>
25+
<Route path="/home">
26+
<HomePage />
27+
</Route>
28+
</MemoryRouter>,
29+
node
30+
);
31+
32+
expect(typeof params).toBe("object");
33+
expect(Object.keys(params)).toHaveLength(0);
34+
});
35+
});
36+
37+
describe("when the path has some params", () => {
38+
it("returns a hash of the URL params and their values", () => {
39+
let params;
40+
41+
function BlogPost() {
42+
params = useParams();
43+
return null;
44+
}
45+
46+
renderStrict(
47+
<MemoryRouter initialEntries={["/blog/cupcakes"]}>
48+
<Route path="/blog/:slug">
49+
<BlogPost />
50+
</Route>
51+
</MemoryRouter>,
52+
node
53+
);
54+
55+
expect(typeof params).toBe("object");
56+
expect(params).toMatchObject({
57+
slug: "cupcakes"
58+
});
59+
});
60+
61+
describe("a child route", () => {
62+
it("returns a combined hash of the parent and child params", () => {
63+
let params;
64+
65+
function Course() {
66+
params = useParams();
67+
return null;
68+
}
69+
70+
function Users() {
71+
const match = useMatch();
72+
return (
73+
<div>
74+
<h1>Users</h1>
75+
<Route path={`${match.path}/courses/:course`}>
76+
<Course />
77+
</Route>
78+
</div>
79+
);
80+
}
81+
82+
renderStrict(
83+
<MemoryRouter
84+
initialEntries={["/users/mjackson/courses/react-router"]}
85+
>
86+
<Route path="/users/:username">
87+
<Users />
88+
</Route>
89+
</MemoryRouter>,
90+
node
91+
);
92+
93+
expect(typeof params).toBe("object");
94+
expect(params).toMatchObject({
95+
username: "mjackson",
96+
course: "react-router"
97+
});
98+
});
99+
});
100+
});
101+
});

Diff for: ‎packages/react-router/modules/hooks.js

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import React from "react";
2+
import invariant from "tiny-invariant";
3+
4+
import Context from "./RouterContext.js";
5+
6+
const useContext = React.useContext;
7+
8+
export function useMatch() {
9+
if (__DEV__) {
10+
invariant(
11+
typeof useContext === "function",
12+
"You must use React >= 16.8 in order to use useMatch()"
13+
);
14+
}
15+
16+
return useContext(Context).match;
17+
}
18+
19+
export function useParams() {
20+
if (__DEV__) {
21+
invariant(
22+
typeof useContext === "function",
23+
"You must use React >= 16.8 in order to use useParams()"
24+
);
25+
}
26+
27+
return useMatch().params;
28+
}
29+
30+
export function useLocation() {
31+
if (__DEV__) {
32+
invariant(
33+
typeof useContext === "function",
34+
"You must use React >= 16.8 in order to use useLocation()"
35+
);
36+
}
37+
38+
return useContext(Context).location;
39+
}
40+
41+
export function useHistory() {
42+
if (__DEV__) {
43+
invariant(
44+
typeof useContext === "function",
45+
"You must use React >= 16.8 in order to use useHistory()"
46+
);
47+
}
48+
49+
return useContext(Context).history;
50+
}

Diff for: ‎packages/react-router/modules/index.js

+3
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,7 @@ export { default as generatePath } from "./generatePath";
3232
export { default as matchPath } from "./matchPath";
3333
export { default as withRouter } from "./withRouter";
3434

35+
import { useMatch, useParams, useLocation, useHistory } from "./hooks.js";
36+
export { useMatch, useParams, useLocation, useHistory };
37+
3538
export { default as __RouterContext } from "./RouterContext";

9 commit comments

Comments
 (9)

MeiKatz commented on Sep 23, 2019

@MeiKatz
Contributor

Just discovered by accident that you added hooks to RR. Any plans in which version of RR you will release them?

sibelius commented on Sep 23, 2019

@sibelius
Contributor

useRouter would be useful as well

export function useRouter() {
  if (__DEV__) {
    invariant(
      typeof useContext === "function",
      "You must use React >= 16.8 in order to use useHistory()"
    );
  }

  return useContext(Context);
}

daniellskelton commented on Sep 24, 2019

@daniellskelton

dattebayorob commented on Sep 25, 2019

@dattebayorob

I just installed React router 5.1 yesterday, but cannot find where to import the new hooks. Can You guys help me?

visormatt commented on Sep 25, 2019

@visormatt

@dattebayorob

import { useMatch, useParams, useLocation, useHistory } from 'react-router';

If you're using TypeScript be sure to update @types/react-router as well

dattebayorob commented on Sep 26, 2019

@dattebayorob

Got it, thanks

mjackson commented on Sep 26, 2019

@mjackson
MemberAuthor

@sibelius If you have a valid use case, please go ahead and open an issue. We can discuss there 👍

MeiKatz commented on Sep 26, 2019

@MeiKatz
Contributor

@visormatt it's useRouteMatch and not useMatch. Just for correctness

avindra commented on Oct 2, 2019

@avindra

useRouter is more or less congruent to what withRouter was doing. Please consider this a +1 that useRouter should be included.

Not only is it more consistent with what users are already expecting from withRouter, but it leads to less re-reading of the react-router context when you need to use multiple items from the context. Separately exposing each item from the context seems sub-optimal (you by definition, end up using multiple useContext calls).

Please sign in to comment.