Skip to content

Instantly share code, notes, and snippets.

@staydecent
Created May 30, 2020 00:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save staydecent/ad07df7152da8f3a9d137b0595ceccf8 to your computer and use it in GitHub Desktop.
Save staydecent/ad07df7152da8f3a9d137b0595ceccf8 to your computer and use it in GitHub Desktop.
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