Files
multica/packages/views/navigation/context.tsx
Naiyuan Qing bd1fb10afa chore: react-doctor cleanup — button types, useContext→use(), toSorted, error fixes (#3350)
- Add explicit type="button" to 61 <button> elements missing the attribute
- Replace useContext() with React 19 use() across 16 context consumers
- Replace [...arr].sort() with arr.toSorted() in 12 web/desktop files
  (mobile excluded — Hermes lacks toSorted support)
- Fix rules-of-hooks violation: useSidebar try/catch → useSidebarSafe null check
- Fix nested component definition: useMemo wrapping HeaderRight → useCallback
- Fix missing ARIA: add aria-expanded + aria-controls to combobox in create-squad

React Doctor score: 23 → 30. No behavioral changes, no business logic modified.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 14:57:07 +08:00

50 lines
1.6 KiB
TypeScript

"use client";
import { createContext, use, useMemo, useTransition } from "react";
import type { NavigationAdapter } from "./types";
const NavigationContext = createContext<NavigationAdapter | null>(null);
const NavigationPendingContext = createContext<boolean>(false);
export function NavigationProvider({
value,
children,
}: {
value: NavigationAdapter;
children: React.ReactNode;
}) {
// Wrap push/replace in startTransition so any caller of useNavigation()
// (sidebar AppLink, command palette, modal post-create jumps) gets a
// React pending signal during route commit. On web this stays true until
// Next.js commits the new RSC payload; on desktop it flips off quickly
// because react-router commits synchronously — both are correct.
const [isPending, startTransition] = useTransition();
const wrapped = useMemo<NavigationAdapter>(
() => ({
...value,
push: (path: string) => startTransition(() => value.push(path)),
replace: (path: string) => startTransition(() => value.replace(path)),
}),
[value],
);
return (
<NavigationContext.Provider value={wrapped}>
<NavigationPendingContext.Provider value={isPending}>
{children}
</NavigationPendingContext.Provider>
</NavigationContext.Provider>
);
}
export function useNavigation(): NavigationAdapter {
const ctx = use(NavigationContext);
if (!ctx)
throw new Error("useNavigation must be used within NavigationProvider");
return ctx;
}
/** True while a transition-wrapped push/replace is committing. */
export function useIsNavigating(): boolean {
return use(NavigationPendingContext);
}