Skip to content

Commit

Permalink
Add some hooks for React 16.8 users
Browse files Browse the repository at this point in the history
- useMatch
- useParams
- useLocation
- useHistory
  • Loading branch information
mjackson committed Sep 19, 2019
1 parent 19835fb commit d6224d6
Show file tree
Hide file tree
Showing 8 changed files with 285 additions and 23 deletions.
24 changes: 12 additions & 12 deletions 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
}
}
22 changes: 11 additions & 11 deletions 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
}
}
34 changes: 34 additions & 0 deletions 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(
<MemoryRouter initialEntries={["/home"]}>
<Route path="/home">
<HomePage />
</Route>
</MemoryRouter>,
node
);

expect(typeof history).toBe("object");
expect(typeof history.push).toBe("function");
});
});
36 changes: 36 additions & 0 deletions 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(
<MemoryRouter initialEntries={["/home"]}>
<Route path="/home">
<HomePage />
</Route>
</MemoryRouter>,
node
);

expect(typeof location).toBe("object");
expect(location).toMatchObject({
pathname: "/home"
});
});
});
38 changes: 38 additions & 0 deletions 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(
<MemoryRouter initialEntries={["/home"]}>
<Route path="/home">
<HomePage />
</Route>
</MemoryRouter>,
node
);

expect(typeof match).toBe("object");
expect(match).toMatchObject({
path: "/home",
url: "/home",
isExact: true
});
});
});
101 changes: 101 additions & 0 deletions 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(
<MemoryRouter initialEntries={["/home"]}>
<Route path="/home">
<HomePage />
</Route>
</MemoryRouter>,
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(
<MemoryRouter initialEntries={["/blog/cupcakes"]}>
<Route path="/blog/:slug">
<BlogPost />
</Route>
</MemoryRouter>,
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 (
<div>
<h1>Users</h1>
<Route path={`${match.path}/courses/:course`}>
<Course />
</Route>
</div>
);
}

renderStrict(
<MemoryRouter
initialEntries={["/users/mjackson/courses/react-router"]}
>
<Route path="/users/:username">
<Users />
</Route>
</MemoryRouter>,
node
);

expect(typeof params).toBe("object");
expect(params).toMatchObject({
username: "mjackson",
course: "react-router"
});
});
});
});
});
50 changes: 50 additions & 0 deletions 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;
}
3 changes: 3 additions & 0 deletions packages/react-router/modules/index.js
Expand Up @@ -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";

9 comments on commit d6224d6

@MeiKatz
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@sibelius
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
}

@dskelton-r7
Copy link

@dskelton-r7 dskelton-r7 commented on d6224d6 Sep 24, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dattebayorob
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@visormatt
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dattebayorob

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

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

@dattebayorob
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, thanks

@mjackson
Copy link
Member Author

@mjackson mjackson commented on d6224d6 Sep 26, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@MeiKatz
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@avindra
Copy link

@avindra avindra commented on d6224d6 Oct 2, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.