Routing
Vanilla Routing with Context
tsx
// context/navigation.ts
import { createContext, useState, useEffect } from "react";
const NavigationContext = createContext();
function NavigationProvider({ children }) {
const [currentPath, setCurrentPath] = useState(window.location.pathname);
// handle front and back arrows
useEffect(() => {
const handler = () => {
setCurrentPath(window.location.pathname);
};
window.addEventListener("popstate", handler);
return () => {
window.removeEventListener("popstate", handler);
};
}, []);
const navigate = (to) => {
window.history.pushState({}, "", to);
setCurrentPath(to);
};
return (
<NavigationContext.Provider value={{ currentPath, navigate }}>
{children}
</NavigationContext.Provider>
);
}
export { NavigationProvider };
export default NavigationContext;
tsx
// Route component for conditionally rendering components
import useNavigation from "../hooks/use-navigation";
function Route({ path, children }) {
const { currentPath } = useNavigation();
if (path === currentPath) {
return children;
}
return null;
}
export default Route;
tsx
// example link component
import classNames from "classnames";
import useNavigation from "../hooks/use-navigation";
function Link({ to, children, className, activeClassName }) {
const { navigate, currentPath } = useNavigation();
const classes = classNames(
"text-blue-500",
className,
currentPath === to && activeClassName // apply styling if currently on this page
);
const handleClick = (event) => {
// handle opening new tab, use default behavior
if (event.metaKey || event.ctrlKey) {
return;
}
event.preventDefault();
navigate(to);
};
return (
<a className={classes} href={to} onClick={handleClick}>
{children}
</a>
);
}
export default Link;
tsx
// Navbar component
import Link from "./Link";
function NavBar() {
const links = [
{ label: "Dropdown", path: "/" },
{ label: "Accordion", path: "/accordion" },
{ label: "Buttons", path: "/buttons" },
];
const renderedLinks = links.map((link) => {
return (
<Link
key={link.label}
to={link.path}
className="mb-3"
activeClassName="font-bold border-l-4 border-blue-500 pl-2"
>
{link.label}
</Link>
);
});
return (
<div className="sticky top-0 overflow-y-scroll flex flex-col items-start">
{renderedLinks}
</div>
);
}
export default NavBar;
tsx
// App.ts
import Sidebar from "./components/Sidebar";
import Route from "./components/Route";
import AccordionPage from "./pages/AccordionPage";
import DropdownPage from "./pages/DropdownPage";
import ButtonPage from "./pages/ButtonPage";
function App() {
return (
<div className="container mx-auto grid grid-cols-6 gap-4 mt-4">
<Sidebar />
<div className="col-span-5">
<Route path="/accordion">
<AccordionPage />
</Route>
<Route path="/">
<DropdownPage />
</Route>
<Route path="/buttons">
<ButtonPage />
</Route>
</div>
</div>
);
}
export default App;
Using React Router library
Documentation here.
tsx
// main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import HomePage from "./pages/HomePage";
import NotFoundPage from "./pages/NotFoundPage";
import ProfilePage from "./pages/ProfilePage";
import ProfilesPage from "./pages/ProfilesPage";
import "./index.css";
const router = createBrowserRouter([
{
path: "/",
element: <HomePage />,
errorElement: <NotFoundPage />,
},
{
path: "/profiles",
element: <ProfilesPage />,
children: [
{
path: "/profiles/:profileId",
element: <ProfilePage />,
},
],
},
]);
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
);
tsx
// ProfilesPage.tsx with child component
import { NavLink, Outlet } from "react-router-dom";
export default function ProfilesPage() {
const profiles = [1, 2, 3, 4, 5];
return (
<div className="flex gap-2">
<div className="flex flex-col gap-2">
{profiles.map((profile) => (
// NavLink allows you to apply styling if the link is active
<NavLink
key={profile}
to={`/profiles/${profile}`}
className={({ isActive }) => {
return isActive ? "text-primary-700" : "";
}}
>
Profile {profile}
</NavLink>
))}
</div>
<!-- Outlet displays child component links if active -->
<Outlet />
</div>
);
}
tsx
// ProfilePage component using query params
import { useParams } from "react-router-dom";
export default function ProfilePage() {
const params = useParams<{ profileId: string }>();
return (
<div>
<h1>Profile Page {params.profileId}</h1>
</div>
);
}