-
-
Notifications
You must be signed in to change notification settings - Fork 151
/
use-hash-location.js
51 lines (42 loc) · 1.54 KB
/
use-hash-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
import { useSyncExternalStore } from "./react-deps.js";
// array of callback subscribed to hash updates
const listeners = {
v: [],
};
const onHashChange = () => listeners.v.forEach((cb) => cb());
// we subscribe to `hashchange` only once when needed to guarantee that
// all listeners are called synchronously
const subscribeToHashUpdates = (callback) => {
if (listeners.v.push(callback) === 1)
addEventListener("hashchange", onHashChange);
return () => {
listeners.v = listeners.v.filter((i) => i !== callback);
if (!listeners.v.length) removeEventListener("hashchange", onHashChange);
};
};
// leading '#' is ignored, leading '/' is optional
const currentHashLocation = () => "/" + location.hash.replace(/^#?\/?/, "");
export const navigate = (to, { state = null } = {}) => {
// calling `replaceState` allows us to set the history
// state without creating an extra entry
const [hash, search] = to.replace(/^#?\/?/, "").split("?");
history.replaceState(
state,
"",
// keep the current pathname, but replace query string and hash
location.pathname +
(search ? `?${search}` : location.search) +
// update location hash, this will cause `hashchange` event to fire
// normalise the value before updating, so it's always preceeded with "#/"
(location.hash = `#/${hash}`)
);
};
export const useHashLocation = ({ ssrPath = "/" } = {}) => [
useSyncExternalStore(
subscribeToHashUpdates,
currentHashLocation,
() => ssrPath
),
navigate,
];
useHashLocation.hrefs = (href) => "#" + href;