fix: synchronize TechLog focus and not-found runtime

This commit is contained in:
DongHyeonka
2026-08-15 22:52:42 +09:00
parent ef1d5cc548
commit 512aa4a1e9
4 changed files with 246 additions and 33 deletions
@@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import { Link, useLocation, useNavigate } from "react-router-dom";
import type {
@@ -28,23 +28,30 @@ export function HomeFocus({
const keys = items.map((item) => item.key);
const activeItem = items.find((item) => item.key === activeKey) ?? items[0];
function replaceFocus(key: FocusKey) {
const search = new URLSearchParams(location.search);
search.set("focus", key);
void navigate(
{
pathname: location.pathname,
search: `?${search.toString()}`,
hash: location.hash,
},
{ replace: true },
);
}
const replaceFocus = useCallback(
(key: FocusKey) => {
const search = new URLSearchParams(location.search);
search.set("focus", key);
void navigate(
{
pathname: location.pathname,
search: `?${search.toString()}`,
hash: location.hash,
},
{ replace: true },
);
},
[location.hash, location.pathname, location.search, navigate],
);
useEffect(() => {
if (!shouldNormalizeFocusUrl(requestedKey, activeKey)) return;
replaceFocus(activeKey);
});
setActiveKey(initialKey);
}, [initialKey]);
useEffect(() => {
if (!shouldNormalizeFocusUrl(requestedKey, initialKey)) return;
replaceFocus(initialKey);
}, [initialKey, replaceFocus, requestedKey]);
function select(key: FocusKey, moveKeyboardFocus = false) {
setActiveKey(key);
+50 -5
View File
@@ -50,6 +50,7 @@ import {
import type { ParsedRouteInput } from "./route-contract.ts";
import {
RegisteredNotFoundProvider,
type RegisteredNotFoundDescriptor,
RouteInputProvider,
} from "./route-input.tsx";
@@ -259,14 +260,14 @@ function RegisteredRoute<RouteIdValue extends string>({
definition,
runtime,
codecs,
NotFoundComponent,
registeredNotFound,
}: {
routeId: RouteIdValue;
buildId: string;
definition: RouteDefinition;
runtime: GroupedRouteRuntimeDefinition;
codecs: RouteCodecRegistry;
NotFoundComponent?: ComponentType;
registeredNotFound?: RegisteredNotFoundRegistration;
}) {
const params = useParams();
const [search] = useSearchParams();
@@ -282,10 +283,35 @@ function RegisteredRoute<RouteIdValue extends string>({
if (!parsed.success) return <InvalidRouteSurface code={parsed.code} />;
const routeInput = parsed.data;
const RuntimeComponent = runtime.Component;
const notFoundDescriptor: RegisteredNotFoundDescriptor | undefined =
registeredNotFound
? {
...registeredNotFound,
render: () => {
const NotFoundRuntime = registeredNotFound.runtime.Component;
return (
<Suspense
fallback={
<RouteLoadingSurface
definition={registeredNotFound.definition}
/>
}
>
<ChunkRecoveryBoundary
chunkId={registeredNotFound.definition.chunkId}
recover={recovery.recoverChunk}
>
<NotFoundRuntime />
</ChunkRecoveryBoundary>
</Suspense>
);
},
}
: undefined;
const content = (
<RouteInputProvider input={routeInput}>
<RegisteredNotFoundProvider Component={NotFoundComponent}>
<RegisteredNotFoundProvider descriptor={notFoundDescriptor}>
<CanonicalRouteRedirect
input={routeInput}
definition={definition}
@@ -329,6 +355,12 @@ type GroupedRouteRuntimeDefinition = Readonly<{
Component: ComponentType;
}>;
type RegisteredNotFoundRegistration = Readonly<{
definition: RouteDefinition;
runtime: GroupedRouteRuntimeDefinition;
buildId: string;
}>;
type GroupedRouteRegistry = Readonly<Record<string, RouteDefinition>>;
type GroupedRouteRuntime = Readonly<
Record<string, GroupedRouteRuntimeDefinition>
@@ -352,7 +384,16 @@ export function createGroupedRouteObjects(
PUBLIC: [],
STUDIO: [],
};
const NotFoundComponent = runtime.NOT_FOUND?.Component;
const notFoundDefinition = registry.NOT_FOUND;
const notFoundRuntime = runtime.NOT_FOUND;
const registeredNotFound =
notFoundDefinition && notFoundRuntime
? {
definition: notFoundDefinition,
runtime: notFoundRuntime,
buildId,
}
: undefined;
for (const definition of Object.values(registry)) {
const routeId = definition.routeId;
const routeRuntime = runtime[definition.routeId];
@@ -366,7 +407,11 @@ export function createGroupedRouteObjects(
definition={definition}
runtime={routeRuntime}
codecs={codecs}
NotFoundComponent={NotFoundComponent}
registeredNotFound={
definition.layoutGroup === notFoundDefinition?.layoutGroup
? registeredNotFound
: undefined
}
/>
);
if (definition.path === "/") {
+22 -7
View File
@@ -5,10 +5,23 @@ import {
useContext,
} from "react";
import type { RouteDefinition } from "../../contracts/routes.ts";
import type { ParsedRouteInput, RouteId } from "./route-contract.ts";
const RouteInputContext = createContext<ParsedRouteInput<string> | null>(null);
const RegisteredNotFoundContext = createContext<ComponentType | null>(null);
export type RegisteredNotFoundDescriptor = Readonly<{
definition: RouteDefinition;
runtime: Readonly<{
moduleId: string;
Component: ComponentType;
}>;
buildId: string;
render(): ReactNode;
}>;
const RegisteredNotFoundContext =
createContext<RegisteredNotFoundDescriptor | null>(null);
export function RouteInputProvider<RouteIdValue extends string>({
input,
@@ -33,21 +46,23 @@ export function useRouteInput<
}
export function RegisteredNotFoundProvider({
Component,
descriptor,
children,
}: Readonly<{
Component?: ComponentType;
descriptor?: RegisteredNotFoundDescriptor;
children: ReactNode;
}>) {
return (
<RegisteredNotFoundContext.Provider value={Component ?? null}>
<RegisteredNotFoundContext.Provider value={descriptor ?? null}>
{children}
</RegisteredNotFoundContext.Provider>
);
}
export function RegisteredNotFoundRoute() {
const Component = useContext(RegisteredNotFoundContext);
if (!Component) throw new Error("Registered not-found runtime is required");
return <Component />;
const descriptor = useContext(RegisteredNotFoundContext);
if (!descriptor) {
throw new Error("Registered not-found runtime is required");
}
return descriptor.render();
}