diff --git a/packages/react-router-dom/.size-snapshot.json b/packages/react-router-dom/.size-snapshot.json
index 214d4e31f8..69f3b3689f 100644
--- a/packages/react-router-dom/.size-snapshot.json
+++ b/packages/react-router-dom/.size-snapshot.json
@@ -1,26 +1,26 @@
{
"esm/react-router-dom.js": {
- "bundled": 8797,
- "minified": 5223,
- "gzipped": 1682,
+ "bundled": 9444,
+ "minified": 5645,
+ "gzipped": 1786,
"treeshaked": {
"rollup": {
- "code": 379,
- "import_statements": 355
+ "code": 2272,
+ "import_statements": 432
},
"webpack": {
- "code": 1612
+ "code": 3572
}
}
},
"umd/react-router-dom.js": {
- "bundled": 129625,
- "minified": 46107,
- "gzipped": 14073
+ "bundled": 131334,
+ "minified": 46991,
+ "gzipped": 14275
},
"umd/react-router-dom.min.js": {
- "bundled": 86293,
- "minified": 29465,
- "gzipped": 9792
+ "bundled": 87085,
+ "minified": 29827,
+ "gzipped": 9885
}
}
diff --git a/packages/react-router/.size-snapshot.json b/packages/react-router/.size-snapshot.json
index 1f5d7e7739..3a066796a8 100644
--- a/packages/react-router/.size-snapshot.json
+++ b/packages/react-router/.size-snapshot.json
@@ -1,26 +1,26 @@
{
"esm/react-router.js": {
- "bundled": 23515,
- "minified": 13336,
- "gzipped": 3696,
+ "bundled": 24890,
+ "minified": 14513,
+ "gzipped": 3839,
"treeshaked": {
"rollup": {
- "code": 2376,
+ "code": 2389,
"import_statements": 470
},
"webpack": {
- "code": 3743
+ "code": 3758
}
}
},
"umd/react-router.js": {
- "bundled": 102011,
- "minified": 36085,
- "gzipped": 11534
+ "bundled": 103056,
+ "minified": 36690,
+ "gzipped": 11666
},
"umd/react-router.min.js": {
- "bundled": 62277,
- "minified": 21931,
- "gzipped": 7707
+ "bundled": 62526,
+ "minified": 22088,
+ "gzipped": 7748
}
}
diff --git a/packages/react-router/modules/__tests__/useHistory-test.js b/packages/react-router/modules/__tests__/useHistory-test.js
new file mode 100644
index 0000000000..98a6264f8e
--- /dev/null
+++ b/packages/react-router/modules/__tests__/useHistory-test.js
@@ -0,0 +1,34 @@
+import React from "react";
+import ReactDOM from "react-dom";
+import { MemoryRouter, Route, useHistory } from "react-router";
+
+import renderStrict from "./utils/renderStrict.js";
+
+describe("useHistory", () => {
+ const node = document.createElement("div");
+
+ afterEach(() => {
+ ReactDOM.unmountComponentAtNode(node);
+ });
+
+ it("returns the history object", () => {
+ let history;
+
+ function HomePage() {
+ history = useHistory();
+ return null;
+ }
+
+ renderStrict(
+
+
+
+
+ ,
+ node
+ );
+
+ expect(typeof history).toBe("object");
+ expect(typeof history.push).toBe("function");
+ });
+});
diff --git a/packages/react-router/modules/__tests__/useLocation-test.js b/packages/react-router/modules/__tests__/useLocation-test.js
new file mode 100644
index 0000000000..1d9571bd33
--- /dev/null
+++ b/packages/react-router/modules/__tests__/useLocation-test.js
@@ -0,0 +1,36 @@
+import React from "react";
+import ReactDOM from "react-dom";
+import { MemoryRouter, Route, useLocation } from "react-router";
+
+import renderStrict from "./utils/renderStrict.js";
+
+describe("useLocation", () => {
+ const node = document.createElement("div");
+
+ afterEach(() => {
+ ReactDOM.unmountComponentAtNode(node);
+ });
+
+ it("returns the current location object", () => {
+ let location;
+
+ function HomePage() {
+ location = useLocation();
+ return null;
+ }
+
+ renderStrict(
+
+
+
+
+ ,
+ node
+ );
+
+ expect(typeof location).toBe("object");
+ expect(location).toMatchObject({
+ pathname: "/home"
+ });
+ });
+});
diff --git a/packages/react-router/modules/__tests__/useMatch-test.js b/packages/react-router/modules/__tests__/useMatch-test.js
new file mode 100644
index 0000000000..9e31afd7f5
--- /dev/null
+++ b/packages/react-router/modules/__tests__/useMatch-test.js
@@ -0,0 +1,38 @@
+import React from "react";
+import ReactDOM from "react-dom";
+import { MemoryRouter, Route, useMatch } from "react-router";
+
+import renderStrict from "./utils/renderStrict.js";
+
+describe("useMatch", () => {
+ const node = document.createElement("div");
+
+ afterEach(() => {
+ ReactDOM.unmountComponentAtNode(node);
+ });
+
+ it("returns the match object", () => {
+ let match;
+
+ function HomePage() {
+ match = useMatch();
+ return null;
+ }
+
+ renderStrict(
+
+
+
+
+ ,
+ node
+ );
+
+ expect(typeof match).toBe("object");
+ expect(match).toMatchObject({
+ path: "/home",
+ url: "/home",
+ isExact: true
+ });
+ });
+});
diff --git a/packages/react-router/modules/__tests__/useParams-test.js b/packages/react-router/modules/__tests__/useParams-test.js
new file mode 100644
index 0000000000..c80358a087
--- /dev/null
+++ b/packages/react-router/modules/__tests__/useParams-test.js
@@ -0,0 +1,101 @@
+import React from "react";
+import ReactDOM from "react-dom";
+import { MemoryRouter, Route, useMatch, useParams } from "react-router";
+
+import renderStrict from "./utils/renderStrict.js";
+
+describe("useParams", () => {
+ const node = document.createElement("div");
+
+ afterEach(() => {
+ ReactDOM.unmountComponentAtNode(node);
+ });
+
+ describe("when the path has no params", () => {
+ it("returns an empty hash", () => {
+ let params;
+
+ function HomePage() {
+ params = useParams();
+ return null;
+ }
+
+ renderStrict(
+
+
+
+
+ ,
+ node
+ );
+
+ expect(typeof params).toBe("object");
+ expect(Object.keys(params)).toHaveLength(0);
+ });
+ });
+
+ describe("when the path has some params", () => {
+ it("returns a hash of the URL params and their values", () => {
+ let params;
+
+ function BlogPost() {
+ params = useParams();
+ return null;
+ }
+
+ renderStrict(
+
+
+
+
+ ,
+ node
+ );
+
+ expect(typeof params).toBe("object");
+ expect(params).toMatchObject({
+ slug: "cupcakes"
+ });
+ });
+
+ describe("a child route", () => {
+ it("returns a combined hash of the parent and child params", () => {
+ let params;
+
+ function Course() {
+ params = useParams();
+ return null;
+ }
+
+ function Users() {
+ const match = useMatch();
+ return (
+
+
Users
+
+
+
+
+ );
+ }
+
+ renderStrict(
+
+
+
+
+ ,
+ node
+ );
+
+ expect(typeof params).toBe("object");
+ expect(params).toMatchObject({
+ username: "mjackson",
+ course: "react-router"
+ });
+ });
+ });
+ });
+});
diff --git a/packages/react-router/modules/hooks.js b/packages/react-router/modules/hooks.js
new file mode 100644
index 0000000000..1a4935fd81
--- /dev/null
+++ b/packages/react-router/modules/hooks.js
@@ -0,0 +1,50 @@
+import React from "react";
+import invariant from "tiny-invariant";
+
+import Context from "./RouterContext.js";
+
+const useContext = React.useContext;
+
+export function useMatch() {
+ if (__DEV__) {
+ invariant(
+ typeof useContext === "function",
+ "You must use React >= 16.8 in order to use useMatch()"
+ );
+ }
+
+ return useContext(Context).match;
+}
+
+export function useParams() {
+ if (__DEV__) {
+ invariant(
+ typeof useContext === "function",
+ "You must use React >= 16.8 in order to use useParams()"
+ );
+ }
+
+ return useMatch().params;
+}
+
+export function useLocation() {
+ if (__DEV__) {
+ invariant(
+ typeof useContext === "function",
+ "You must use React >= 16.8 in order to use useLocation()"
+ );
+ }
+
+ return useContext(Context).location;
+}
+
+export function useHistory() {
+ if (__DEV__) {
+ invariant(
+ typeof useContext === "function",
+ "You must use React >= 16.8 in order to use useHistory()"
+ );
+ }
+
+ return useContext(Context).history;
+}
diff --git a/packages/react-router/modules/index.js b/packages/react-router/modules/index.js
index 6a2a40126f..2dcf60e2a3 100644
--- a/packages/react-router/modules/index.js
+++ b/packages/react-router/modules/index.js
@@ -32,4 +32,7 @@ export { default as generatePath } from "./generatePath";
export { default as matchPath } from "./matchPath";
export { default as withRouter } from "./withRouter";
+import { useMatch, useParams, useLocation, useHistory } from "./hooks.js";
+export { useMatch, useParams, useLocation, useHistory };
+
export { default as __RouterContext } from "./RouterContext";