Created
May 30, 2020 00:21
-
-
Save staydecent/ad07df7152da8f3a9d137b0595ceccf8 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
createContext, | |
useContext, | |
useEffect, | |
useReducer, | |
useRef | |
} from "preact/compat"; | |
import { getRouteComponent, getAllRoutes, getHref } from "./util"; | |
const EMPTY = Symbol(); | |
const Context = createContext("Router"); | |
let allRoutes = EMPTY; | |
export function Link({ to, args = {}, queries = {}, children, ...props }) { | |
if (allRoutes === EMPTY) { | |
throw new Error("<Link /> must be child of <RouteProvider />"); | |
} | |
const rule = allRoutes[to]; | |
if (!rule) { | |
console.error("No route found for name: " + name); | |
return; | |
} | |
const href = getHref({ rule, args, queries }); | |
console.log({ href }); | |
return ( | |
<Context.Consumer> | |
{routerContext => { | |
const onClick = ev => { | |
ev.preventDefault(); | |
routerContext.routeTo(allRoutes)(href); | |
}; | |
return ( | |
<a href={href} onClick={onClick} {...props}> | |
{children} | |
</a> | |
); | |
}} | |
</Context.Consumer> | |
); | |
} | |
function useRouterState() { | |
const [, update] = useReducer(s => s + 1, 0); | |
const pathRef = useRef(EMPTY); | |
const routeRef = useRef(EMPTY); | |
const setRoute = route => { | |
if (routeRef.current === EMPTY || routeRef.current.name !== route.name) { | |
routeRef.current = route; | |
update(); | |
} | |
}; | |
const setCurrentPath = (path, skipUpdate) => { | |
pathRef.current = path; | |
update(); | |
}; | |
const routeTo = routes => path => { | |
if (path !== pathRef.current) { | |
window.history.pushState(null, null, path); | |
setCurrentPath(path); | |
} | |
}; | |
return { | |
route: routeRef.current, | |
setRoute, | |
currentPath: pathRef.current, | |
setCurrentPath, | |
routeTo | |
}; | |
} | |
export function createRouter(routes) { | |
allRoutes = getAllRoutes(routes); | |
function RouteProvider(props) { | |
const value = useRouterState(); | |
useEffect(() => { | |
const onPop = ev => { | |
const url = window.location.pathname; | |
const currentPath = value.currentPath; | |
if (currentPath !== url) { | |
window.history.replaceState(null, null, url); | |
value.setCurrentPath(url); | |
} | |
}; | |
window.addEventListener("popstate", onPop); | |
return () => window.removeEventListener("popstate", onPop); | |
}, []); | |
useEffect(() => { | |
if (value.currentPath === EMPTY) { | |
console.log("INIT"); | |
value.setCurrentPath(window.location.pathname + window.location.search); | |
} | |
}, []); | |
return <Context.Provider value={value}>{props.children}</Context.Provider>; | |
} | |
function useRouter() { | |
const ctx = useContext(Context); | |
if (ctx === EMPTY) { | |
throw new Error("Component must be wrapped with <RouteProvider>"); | |
} | |
return ctx; | |
} | |
function Router(props) { | |
const localRoutes = props.routes || routes; | |
const { routeTo, currentPath, setRoute } = useRouter(); | |
if (currentPath === EMPTY) { | |
return; | |
} | |
const [Component, newRoute] = getRouteComponent(localRoutes, currentPath); | |
if (newRoute) { | |
setRoute(newRoute); | |
} | |
return typeof Component === "function" ? ( | |
<Component routeTo={routeTo(localRoutes)} /> | |
) : ( | |
Component | |
); | |
} | |
return { RouteProvider, Router, useRouter }; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment