Skip to content

Instantly share code, notes, and snippets.

@danielknell
Last active November 25, 2020 11:03
Show Gist options
  • Save danielknell/45d9a8dd491f5988fec038eb11ebc00d to your computer and use it in GitHub Desktop.
Save danielknell/45d9a8dd491f5988fec038eb11ebc00d to your computer and use it in GitHub Desktop.
Config based routing in react.
import React from "react";
import { BrowserRouter as Router } from "react-router-dom";
import { Routes, Link, RouteDefinition } from "./router";
const Home: React.FC<{}> = () => {
return (
<>
<h1>Home</h1>
<p><Link name="hello" params={{name: "World"}}>Hello World</Link></p>
<p><Link name="hello" params={{name: "Bob"}}>Hello Bob</Link></p>
</>
);
};
const Hello: React.FC<{ name: string }> = ({ name }) => {
return (
<>
<h1>Hello { name }!</h1>
<p><Link name="home">Go Home</Link></p>
</>
);
};
const routes: RouteDefinition[] = [
{
name: "home",
path: "/",
render: () => <HomeScene />,
},
{
name: "hello",
path: "/hello/:name",
render: ({ match }) => <Hello name={match.params.name} />,
},
{
name: "error.404",
render: () => <h1>Not Found</h1>,
},
];
const Application = () => {
return (
<Router>
<Routes routes={routes} />
</Router>
);
};
import React from "react";
import {
Link as BaseLink,
NavLink as BaseNavLink,
Switch,
Route,
generatePath,
RouteComponentProps,
RouteChildrenProps,
match,
} from "react-router-dom";
import { LocationState, Location } from "history";
interface LinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
component?: React.ComponentType<any>;
name: string;
params?: { [paramName: string]: string | number | boolean | undefined };
replace?: boolean;
innerRef?: React.Ref<HTMLAnchorElement>;
}
interface NavLinkProps<S = LocationState> extends LinkProps {
activeClassName?: string;
activeStyle?: React.CSSProperties;
exact?: boolean;
strict?: boolean;
isActive?<Params extends { [K in keyof Params]?: string }>(
match: match<Params> | null,
location: Location<S>
): boolean;
location?: Location<S>;
}
export interface RouteDefinition {
name: string;
component?:
| React.ComponentType<RouteComponentProps<any>>
| React.ComponentType<any>;
render?: (props: RouteComponentProps<any>) => React.ReactNode;
children?:
| ((props: RouteChildrenProps<any>) => React.ReactNode)
| React.ReactNode;
path?: string;
exact?: boolean;
sensitive?: boolean;
strict?: boolean;
}
interface RouterProps {
routes: RouteDefinition[];
}
const RouteContext = React.createContext<RouteDefinition[]>([]);
export const Routes: React.FC<RouterProps> = ({ routes }) => {
return (
<RouteContext.Provider value={routes}>
<Switch>
{routes.map(({ name, exact, ...props }) => (
<Route
key={name}
exact={exact === undefined ? true : exact}
{...props}
/>
))}
</Switch>
</RouteContext.Provider>
);
};
const useRoute = (
name: string,
params?: { [paramName: string]: string | number | boolean | undefined }
): string => {
const routes = React.useContext(RouteContext);
const mapping = React.useMemo(() => {
return Object.fromEntries(routes.map((v) => [v.name, v]));
}, [routes]);
const route = mapping[name];
if (route === undefined) {
throw new Error(`Unknown route: ${name}`);
}
if (route.path === undefined) {
throw new Error(`No path defined for route: ${name}`);
}
return generatePath(route.path, params);
};
export const Link: React.FC<LinkProps> = ({ name, params = {}, ...props }) => {
const path = useRoute(name, params);
return <BaseLink to={path} {...props} />;
};
export const NavLink: React.FC<NavLinkProps> = ({
name,
params = {},
...props
}) => {
const path = useRoute(name, params);
return <BaseNavLink to={path} {...props} />;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment