Skip to content

Commit 1e023c9

Browse files
authored
🐛 Handle scrolling to initial hash state in table of contents (#1070)
1 parent 21c4dac commit 1e023c9

1 file changed

Lines changed: 29 additions & 6 deletions

File tree

src/providers/TableOfContentsProvider.tsx

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ export interface TableOfContentsProviderProps {
5151
children: ReactNode;
5252
hashNavigation?: boolean;
5353
}
54-
5554
export const TableOfContentsProvider: FC<TableOfContentsProviderProps> = ({
5655
items,
5756
children,
@@ -62,6 +61,7 @@ export const TableOfContentsProvider: FC<TableOfContentsProviderProps> = ({
6261
const [selected, setSelected] = useState<string | undefined>(items[0]?.value);
6362
const [elements, setElements] = useState<(Element | null)[]>([]);
6463
const [shouldInstantlyJump, setShouldInstantlyJump] = useState(hash !== '');
64+
const initHashStateRef = useRef(false);
6565

6666
const values: string[] = useMemo(
6767
() => items.flatMap((item) => getValues([], item)),
@@ -73,6 +73,11 @@ export const TableOfContentsProvider: FC<TableOfContentsProviderProps> = ({
7373
setElements(values.map((value) => document.getElementById(value)));
7474
}, [values]);
7575

76+
useEffect(() => {
77+
if (hash.length) return;
78+
initHashStateRef.current = true;
79+
}, [hash]);
80+
7681
const isActive = useCallback(
7782
(item: TableOfContentsItemType) => {
7883
if (item.value === selected) return true;
@@ -154,12 +159,30 @@ export const TableOfContentsProvider: FC<TableOfContentsProviderProps> = ({
154159
newSelectedIndex = index;
155160
}
156161
}
157-
if (newSelectedIndex !== -1 && values.at(newSelectedIndex) !== undefined) {
158-
setSelected(values[newSelectedIndex]);
159-
if (hashNavigation) {
160-
navigate(`#${values[newSelectedIndex]}`, { replace: true });
161-
}
162+
163+
if (newSelectedIndex === -1 || values.at(newSelectedIndex) === undefined)
164+
return;
165+
166+
const targetValue = hash.replace('#', '');
167+
if (
168+
!initHashStateRef.current &&
169+
targetValue.length &&
170+
values.includes(targetValue)
171+
) {
172+
handleSetSelected(targetValue, {
173+
behavior: 'instant',
174+
shouldInstantlyJumpOnMount: true,
175+
});
176+
initHashStateRef.current = true;
177+
return;
178+
}
179+
180+
setSelected(values[newSelectedIndex]);
181+
if (hashNavigation) {
182+
navigate(`#${values[newSelectedIndex]}`, { replace: true });
162183
}
184+
// this effect handles scroll navigation and should not be triggered on hash change
185+
// eslint-disable-next-line react-hooks/exhaustive-deps
163186
}, [hashNavigation, navigate, values, visible]);
164187
/* v8 ignore end */
165188

0 commit comments

Comments
 (0)