Skip to content

Commit 8784112

Browse files
conico974Nicolas Dorseuil
andauthored
Add HashContext and HashProvider for managing URL hash state (#3541)
Co-authored-by: Nicolas Dorseuil <[email protected]>
1 parent a650b58 commit 8784112

File tree

4 files changed

+61
-25
lines changed

4 files changed

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

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

Lines changed: 5 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,17 @@ 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+
if (!isExternal) {
84+
updateHashFromUrl(href);
85+
}
8186

8287
if (insights) {
8388
trackEvent(insights, undefined, { immediate: isExternalWithOrigin });

0 commit comments

Comments
 (0)