forked from molefrog/wouter
-
Notifications
You must be signed in to change notification settings - Fork 0
/
use-location.js
87 lines (74 loc) · 2.63 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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import { useSyncExternalStore, useEvent } from "./react-deps.js";
import { absolutePath, relativePath } from "./paths.js";
/**
* History API docs @see https://developer.mozilla.org/en-US/docs/Web/API/History
*/
const eventPopstate = "popstate";
const eventPushState = "pushState";
const eventReplaceState = "replaceState";
const eventHashchange = "hashchange";
export const events = [
eventPopstate,
eventPushState,
eventReplaceState,
eventHashchange,
];
const subscribeToLocationUpdates = (callback) => {
for (const event of events) {
addEventListener(event, callback);
}
return () => {
for (const event of events) {
removeEventListener(event, callback);
}
};
};
export const useLocationProperty = (fn, ssrFn) =>
useSyncExternalStore(subscribeToLocationUpdates, fn, ssrFn);
const currentSearch = () => {
if (typeof location != "undefined") location.search;
};
export const useSearch = () => useLocationProperty(currentSearch);
const currentPathname = () => {
if (typeof location != "undefined") return location.pathname;
};
export const usePathname = ({ ssrPath } = {}) => {
if (ssrPath) return ssrPath;
return useLocationProperty(
currentPathname,
ssrPath ? () => ssrPath : currentPathname
);
};
export const navigate = (to, { replace = false } = {}) =>
history[replace ? eventReplaceState : eventPushState](null, "", to);
// 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.
// (This is achieved via `useEvent`.)
const useLocation = (opts = {}) => [
relativePath(opts.base, usePathname(opts)),
useEvent((to, navOpts) => navigate(absolutePath(to, opts.base), navOpts)),
];
export default useLocation;
// 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
if (typeof history !== "undefined") {
for (const type of [eventPushState, eventReplaceState]) {
const original = history[type];
// TODO: we should be using unstable_batchedUpdates to avoid multiple re-renders,
// however that will require an additional peer dependency on react-dom.
// See: https://github.com/reactwg/react-18/discussions/86#discussioncomment-1567149
history[type] = function () {
const result = original.apply(this, arguments);
const event = new Event(type);
event.arguments = arguments;
dispatchEvent(event);
return result;
};
}
}