on d760cbf help sign in
bridge/src/lib/useRouter.js
111 lines 2.7 kB history raw
1import { useCallback, useMemo, useState, useEffect } from 'react';
2import { last, includes as _includes } from 'lodash';
4/**
5 * @param primary whether or not this is the top-level router
6 * @param names map of string to view key, used for consumer references
7 * @param views map of view keys to react component views
8 * @param initialRoutes initial set of routes following the { key, data } format
9 */
10export default function useRouter({
11 primary = false,
12 names = {},
13 views = {},
14 initialRoutes = [],
15}) {
16 const [routes, setRoutes] = useState(initialRoutes);
18 const push = useCallback(
19 (key, data) => {
20 if (!views[key]) {
21 throw new Error(
22 `Invalid Route: ${key}. Possible Routes: ${Object.keys(views).join(
23 ', '
24 )}`
25 );
26 }
27 setRoutes(routes => [...routes, { key, data }]);
28 },
29 [setRoutes, views]
30 );
31 const popAndPush = useCallback(
32 (key, data = {}) => {
33 if (!views[key]) {
34 throw new Error(
35 `Invalid Route: ${key}. Possible Routes: ${Object.keys(views).join(
36 ', '
37 )}`
38 );
39 }
41 setRoutes(routes => [
42 ...routes.slice(0, routes.length - 1),
43 { key, data },
44 ]);
45 },
46 [setRoutes, views]
47 );
48 const pop = useCallback(
49 (count = 1) =>
50 setRoutes(routes =>
51 routes.length > 1 ? [...routes.slice(0, routes.length - count)] : routes
52 ),
53 // ^ can't pop root route
54 [setRoutes]
55 );
56 const peek = useCallback(() => last(routes), [routes]);
57 const replaceWith = useCallback(routes => setRoutes(() => routes), [
58 setRoutes,
59 ]);
60 const reset = useCallback(() => setRoutes(initialRoutes), [
61 setRoutes,
62 initialRoutes,
63 ]);
64 const includes = useCallback(key => _includes(routes.map(r => r.key), key), [
65 routes,
66 ]);
67 const size = routes.length;
68 const data = useMemo(() => last(routes).data, [routes]);
69 const Route = useMemo(() => views[peek().key], [views, peek]);
71 // Scroll to top of page with each route transition
72 useEffect(() => {
73 window.scrollTo(0, 0);
74 }, [routes]);
76 // capture browser pop in primary router
77 useEffect(() => {
78 const oldPopState = window.onpopstate;
80 window.history.pushState(null, null, null);
82 window.onpopstate = e => {
83 if (size <= 1) {
84 // if this is the root route, give the event to parent router
85 return oldPopState(e);
86 }
88 window.history.pushState(null, null, null);
89 pop();
90 };
92 return () => {
93 window.onpopstate = oldPopState;
94 };
95 }, [size, pop]);
97 return {
98 Route,
99 names,
100 routes,
101 push,
102 popAndPush,
103 pop,
104 replaceWith,
105 reset,
106 peek,
107 size,
108 includes,
109 data,
110 };