Skip to content

Instantly share code, notes, and snippets.

@dbtek
Created September 14, 2024 18:33
Show Gist options
  • Save dbtek/98f0c3826327bebc0290313eba76df1f to your computer and use it in GitHub Desktop.
Save dbtek/98f0c3826327bebc0290313eba76df1f to your computer and use it in GitHub Desktop.
Dead simple router for React
import React, { createContext, useEffect, useState } from 'react';
// See https://create-react-app.dev/docs/using-the-public-folder/
const PUBLIC_URL = '';
/**
* Router usage:
* <Router>
* <Route path="/" component={HomePage} />
* <Route path="/about" component={AboutPage} />
* </Router>
*
* function HomePage() {
* const { pathname, nav } = useRouter();
* return (
* <div>
* <h1>Home Page</h1>
* <button onClick={() => nav('/about')}>Go to About</button>
* </div>
* );
* }
* >
*/
export const RouterContext = createContext<{
// current pathname
pathname: string;
// active search params
searchParams: URLSearchParams;
// updates a query param
setSearchParam: (k: string, v: string) => void;
// navigate function
nav: (path: string) => void;
}>({
pathname: window.location.pathname,
searchParams: new URLSearchParams(window.location.search),
setSearchParam: (k: string, v: string) => { },
nav: () => { }
});
export function useRouter() {
return React.useContext(RouterContext);
}
export function Router(props: {
render?: (props: { pathname: string }) => JSX.Element;
children?: JSX.Element;
}) {
const [pathname, setPathname] = useState(getPathname());
const [searchParams, setSearchParams] = useState(new URLSearchParams(window.location.search));
useEffect(() => {
const handleLocationChange = () => {
const newPathname = getPathname();
setPathname(newPathname || '/');
setSearchParams(new URLSearchParams(window.location.search));
};
// Add event listener for navigate (triggered by back/forward navigation)
window.addEventListener('navigate', handleLocationChange);
// Cleanup the event listener on component unmount
return () => {
window.removeEventListener('navigate', handleLocationChange);
};
}, []);
function nav(path: string, qs?: string) {
window.history.pushState({}, '', PUBLIC_URL + path + (qs ? '?' + qs : ''));
const navigationEvent = new PopStateEvent("navigate");
window.dispatchEvent(navigationEvent);
}
function setSearchParam(k: string, v: string) {
const newParams = new URLSearchParams(searchParams.toString());
newParams.set(k, v);
nav(pathname, newParams.toString());
}
return (
<RouterContext.Provider value={{ pathname, nav, searchParams, setSearchParam }}>
{props.render ? props.render({ pathname }) : null}
{props.children}
</RouterContext.Provider>
);
}
export function Route(props: {
path: string;
component: React.ComponentType<{ pathname: string }>;
exact?: boolean;
}) {
const { pathname } = useRouter();
if (props.exact ? (pathname === props.path) : pathname.startsWith(props.path)) {
return (
<props.component pathname={pathname} />
);
}
return null;
}
function getPathname() {
return window.location.pathname.replace(PUBLIC_URL, '') || '/';
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment