7bf4822b27
The breadcrumb "All" button was broken on first click after entering a group because next-view-transitions@0.3.5's useTransitionRouter wraps router.replace() in startTransition + document.startViewTransition, and this combination is incompatible with Next.js 16.2 RSC navigation when only the search params change for the same pathname (e.g. /library?group=foo -> /library). The navigation silently never commits. Extract the library navigation logic into a useLibraryNavigation hook that uses plain useRouter from next/navigation. The data-nav-direction attribute is still set so existing directional CSS keeps working when view transitions fire via popstate. See https://github.com/shuding/next-view-transitions/issues/65 for the upstream incompatibility. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
52 lines
1.9 KiB
TypeScript
52 lines
1.9 KiB
TypeScript
import { useCallback } from 'react';
|
|
import { ReadonlyURLSearchParams, useRouter } from 'next/navigation';
|
|
|
|
import { navigateToLibrary } from '@/utils/nav';
|
|
|
|
/**
|
|
* Hook for navigating between library views (group/subgroup/root) while
|
|
* setting a `data-nav-direction` attribute used by the directional view
|
|
* transition CSS.
|
|
*
|
|
* NOTE: This hook intentionally uses the plain Next.js `useRouter` instead of
|
|
* `useAppRouter` (which wraps router calls in `next-view-transitions`'s
|
|
* `useTransitionRouter`). The wrapped router is incompatible with Next.js
|
|
* 16.2's RSC navigation when only the search params change for the same
|
|
* pathname (e.g. `/library?group=foo` -> `/library`), which previously caused
|
|
* the breadcrumb "All" button to do nothing on the first click after entering
|
|
* a group. See https://github.com/readest/readest/issues/3782 and
|
|
* https://github.com/shuding/next-view-transitions/issues/65.
|
|
*/
|
|
export function useLibraryNavigation(
|
|
searchParams: ReadonlyURLSearchParams | null,
|
|
onBeforeNavigate?: (currentGroup: string) => void,
|
|
) {
|
|
const router = useRouter();
|
|
|
|
return useCallback(
|
|
(targetGroup: string) => {
|
|
const currentGroup = searchParams?.get('group') || '';
|
|
|
|
onBeforeNavigate?.(currentGroup);
|
|
|
|
// Detect and set navigation direction so the view transition CSS can
|
|
// animate forward (entering a group) vs back (returning to a parent or
|
|
// the root) using the appropriate slide direction.
|
|
const direction = currentGroup && !targetGroup ? 'back' : 'forward';
|
|
if (typeof document !== 'undefined') {
|
|
document.documentElement.setAttribute('data-nav-direction', direction);
|
|
}
|
|
|
|
const params = new URLSearchParams(searchParams?.toString());
|
|
if (targetGroup) {
|
|
params.set('group', targetGroup);
|
|
} else {
|
|
params.delete('group');
|
|
}
|
|
|
|
navigateToLibrary(router, `${params.toString()}`);
|
|
},
|
|
[searchParams, router, onBeforeNavigate],
|
|
);
|
|
}
|