forked from molefrog/wouter
-
Notifications
You must be signed in to change notification settings - Fork 0
/
use-location.js
72 lines (56 loc) · 2.28 KB
/
use-location.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import { useEffect, useRef, useState, useCallback } from "./react-deps.js";
export default ({ base = "" } = {}) => {
const [path, update] = useState(currentPathname(base));
const prevPath = useRef(path);
useEffect(() => {
patchHistoryEvents();
// this function checks if the location has been changed since the
// last render and updates the state only when needed.
// unfortunately, we can't rely on `path` value here, since it can be stale,
// that's why we store the last pathname in a ref.
const checkForUpdates = () => {
const pathname = currentPathname(base);
prevPath.current !== pathname && update((prevPath.current = pathname));
};
const events = ["popstate", "pushState", "replaceState"];
events.map(e => addEventListener(e, checkForUpdates));
// it's possible that an update has occurred between render and the effect handler,
// so we run additional check on mount to catch these updates. Based on:
// https://gist.github.com/bvaughn/e25397f70e8c65b0ae0d7c90b731b189
checkForUpdates();
return () => events.map(e => removeEventListener(e, checkForUpdates));
}, []);
// the 2nd argument of the `useLocation` return value is a function
// that allows to perform a navigation.
//
// the function reference should stay the same between re-renders, so that
// it can be passed down as an element prop without any performance concerns.
const navigate = useCallback(
(to, replace) =>
history[replace ? "replaceState" : "pushState"](0, 0, base + to),
[]
);
return [path, navigate];
};
// While History API does have `popstate` event, the only
// proper way to listen to changes via `push/replaceState`
// is to monkey-patch these methods.
//
// See https://stackoverflow.com/a/4585031
let patched = 0;
const patchHistoryEvents = () => {
if (patched) return;
["pushState", "replaceState"].map(type => {
const original = history[type];
history[type] = function() {
const result = original.apply(this, arguments);
const event = new Event(type);
event.arguments = arguments;
dispatchEvent(event);
return result;
};
});
return (patched = 1);
};
const currentPathname = (base, path = location.pathname) =>
!path.indexOf(base) ? path.slice(base.length) || "/" : path;