Router Context & Hooks
Explore how Dinou propagates route parameters, exports navigational React hooks, and maintains SSR data compatibility through isolated request context wrappers.
Key Files Involved:
• Core router context & hooks:./dinou/core/navigation.js
• URL checkers & resolvers:./dinou/core/navigation-utils.js
💡 Overview
In a Server Components ecosystem, client-side files require clean hooks to request navigation transitions, retrieve active routes, and view query parameters. Dinou implements this using the RouterContext context declared in navigation.js, coupled with helpers in navigation-utils.js to normalize paths and detect boundaries.
🔗 1. Router Context
The context stores the active route path string, the navigate action hook, and transition loading states:
export const RouterContext = createContext({
url: "",
navigate: (url) => {
console.warn("navigate called outside Router");
},
isPending: false,
});⚙️ 2. Custom Client Hooks
Dinou exports dedicated client-side hooks to interact with navigation state:
useRouter(): Exposes programmatic methods to control history entries (push,replace,back,forward) or soft-reload components without full reloads (refresh).useNavigationLoading(): Exposes the boolean state indicating if an RSC Flight payload fetch transition is pending in the background.
export function useRouter() {
const context = useContext(RouterContext);
if (!context) {
return {
push: () => {},
replace: () => {},
back: () => {},
forward: () => {},
refresh: () => {},
};
}
return {
push: (href, options) => context.navigate(href, options),
replace: (href, options) => context.navigate(href, { replace: true, ...options }),
back: () => context.back(),
forward: () => context.forward(),
refresh: () => context.refresh(),
};
}
export function useNavigationLoading() {
if (typeof window === "undefined") return false;
const context = useContext(RouterContext);
if (!context || typeof context === "string") return false;
return context.isPending;
}🌐 3. Server-Side Rendering (SSR) Compatibility
Hooks like usePathname() and useSearchParams() must function during initial HTML render on the server, before browser window objects or contexts are initialized.
Dinou solves this by adding a server-side branch inside hooks. If typeof window === "undefined", they dynamically require request-context.js to read active route descriptors from the thread's AsyncLocalStorage wrapper:
export function usePathname() {
// SERVER LOGIC (SSR)
if (typeof window === "undefined") {
try {
const dynamicRequire = typeof __dinou_require__ !== "undefined"
? __dinou_require__
: typeof module !== "undefined" && typeof module.require === "function"
? module.require.bind(module)
: null;
if (dynamicRequire) {
const { getContext } = dynamicRequire("./request-context.js");
const ctx = getContext();
if (ctx && ctx.req) return normalizePath(ctx.req.path);
}
} catch (e) {
console.log("error getContext usePathname", e);
}
}
// CLIENT LOGIC
const context = useContext(RouterContext);
const fullRoute = typeof context === "string" ? context : context.url;
if (typeof fullRoute !== "string") return "";
const path = fullRoute.split("?")[0];
return normalizePath(path);
}