React
@isorouter/react wraps the core router's immutable-snapshot external store with useSyncExternalStore, so navigation re-renders stay correct and tear-free under React 18's concurrent renderer.
Requires React ≥ 18. @isorouter/core is installed automatically.
npm install @isorouter/reactQuick start
import { createRoot } from "react-dom/client";
import { createRouter, Router, Outlet } from "@isorouter/react";
const router = createRouter([
{ path: "/", component: Home },
{ path: "about", component: About },
{
path: "dashboard",
component: DashboardLayout,
children: [
{ index: true, component: Overview },
{ path: "settings", component: Settings },
],
},
] as const);
function DashboardLayout() {
return (
<div>
<h1>Dashboard</h1>
<Outlet />
</div>
);
}
createRoot(document.getElementById("root")!).render(
<Router router={router} notFound={<p>Not found</p>} />,
);createRouter is createCoreRouter with the component type fixed to a React ComponentType. <Router> calls router.start() on mount and router.stop() on unmount.
Components
<Router>
<Router
router={router}
loading={<Spinner />}
notFound={<NotFound />}
error={(err) => <ErrorPage error={err} />}
/>notFound— rendered whensnapshot.status === "not-found".error— called withsnapshot.errorwhensnapshot.status === "error".loading— when there's no matched root component yet (e.g. before the first commit) and neithererrornornotFoundapplies.
Otherwise renders the root matched component, snapshot.components[0].
<Outlet>
Renders the next component in the matched chain at the current nesting depth; renders nothing when there's no matching child. Use it inside a layout.
<Link>
<Link href="/dashboard" activeClassName="active" exact>
Dashboard
</Link>A plain <a> intercepted by the Navigation API — extends AnchorHTMLAttributes<HTMLAnchorElement> and forwards ref and any other props. activeClassName is appended to className when router.isActive(href, { exact }) (default "active"); when active, also sets aria-current="page". See Links & active state.
Hooks
All hooks must be used within <Router>.
| Hook | Returns |
|---|---|
useRouter() | the Router instance. |
useRouterState() | the current RouterSnapshot, re-rendering on every commit. |
useParams<P>() | snapshot.params, typed as P. |
useLocation() | snapshot.url. |
useNavigate() | a referentially-stable (to, opts?) => void bound to router.navigate. |
import { useParams, useNavigate } from "@isorouter/react";
function User() {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
return <button onClick={() => navigate("/")}>User {id} — go home</button>;
}useNavigate() is referentially stable, so it's safe in useEffect dependency arrays and useCallback bodies without re-subscribing.