Skip to content

Commit ad7ccc0

Browse files
author
Nicolas Dorseuil
committed
Add HashContext and HashProvider for managing URL hash state
1 parent f72b6a2 commit ad7ccc0

File tree

4 files changed

+52
-25
lines changed

4 files changed

+52
-25
lines changed

packages/gitbook/src/components/RootLayout/ClientContexts.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type React from 'react';
55
import { TranslateContext } from '@/intl/client';
66
import type { TranslationLanguage } from '@/intl/translations';
77
import { TooltipProvider } from '@radix-ui/react-tooltip';
8+
import { HashProvider } from '../hooks';
89
import { LoadingStateProvider } from '../primitives/LoadingStateProvider';
910

1011
export function ClientContexts(props: {
@@ -16,7 +17,9 @@ export function ClientContexts(props: {
1617
return (
1718
<TranslateContext.Provider value={language}>
1819
<TooltipProvider delayDuration={200}>
19-
<LoadingStateProvider>{children}</LoadingStateProvider>
20+
<HashProvider>
21+
<LoadingStateProvider>{children}</LoadingStateProvider>
22+
</HashProvider>
2023
</TooltipProvider>
2124
</TranslateContext.Provider>
2225
);

packages/gitbook/src/components/hooks/useHash.ts

Lines changed: 0 additions & 24 deletions
This file was deleted.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
'use client';
2+
import React from 'react';
3+
4+
export const HashContext = React.createContext<{
5+
hash: string | null;
6+
updateHashFromUrl: (href: string) => void;
7+
}>({
8+
hash: null,
9+
updateHashFromUrl: () => {},
10+
});
11+
12+
function getHash(): string | null {
13+
if (typeof window === 'undefined') {
14+
return null;
15+
}
16+
return window.location.hash.slice(1);
17+
}
18+
19+
export const HashProvider: React.FC<React.PropsWithChildren<{}>> = ({ children }) => {
20+
const [hash, setHash] = React.useState<string | null>(getHash);
21+
const memoizedHash = React.useMemo(() => hash, [hash]);
22+
const updateHashFromUrl = React.useCallback((href: string) => {
23+
const url = new URL(href, 'http://localhost');
24+
setHash(url.hash.slice(1));
25+
}, []);
26+
return (
27+
<HashContext.Provider value={{ hash: memoizedHash, updateHashFromUrl }}>
28+
{children}
29+
</HashContext.Provider>
30+
);
31+
};
32+
33+
/**
34+
* Hook to get the current hash from the URL.
35+
* @see https://github.com/vercel/next.js/discussions/49465
36+
* We use a different hack than this one, because for same page link it don't work
37+
* We can't use the `hashChange` event because it doesn't fire for `replaceState` and `pushState` which are used by Next.js.
38+
* Since we have a single Link component that handles all links, we can use a context to share the hash.
39+
*/
40+
export function useHash() {
41+
// const params = useParams();
42+
const { hash } = React.useContext(HashContext);
43+
44+
return hash;
45+
}

packages/gitbook/src/components/primitives/Link.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import React from 'react';
66
import { tcls } from '@/lib/tailwind';
77
import { SiteExternalLinksTarget } from '@gitbook/api';
88
import { type TrackEventInput, useTrackEvent } from '../Insights';
9+
import { HashContext } from '../hooks';
910
import { isExternalLink } from '../utils/link';
1011
import { type DesignTokenName, useClassnames } from './StyleProvider';
1112

@@ -71,13 +72,15 @@ export const Link = React.forwardRef(function Link(
7172
) {
7273
const { href, prefetch, children, insights, classNames, className, ...domProps } = props;
7374
const { externalLinksTarget } = React.useContext(LinkSettingsContext);
75+
const { updateHashFromUrl } = React.useContext(HashContext);
7476
const trackEvent = useTrackEvent();
7577
const forwardedClassNames = useClassnames(classNames || []);
7678
const isExternal = isExternalLink(href);
7779
const { target, rel } = getTargetProps(props, { externalLinksTarget, isExternal });
7880

7981
const onClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
8082
const isExternalWithOrigin = isExternalLink(href, window.location.origin);
83+
updateHashFromUrl(href);
8184

8285
if (insights) {
8386
trackEvent(insights, undefined, { immediate: isExternalWithOrigin });

0 commit comments

Comments
 (0)