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";