diff --git a/.changeset/empty-kings-compete.md b/.changeset/empty-kings-compete.md new file mode 100644 index 0000000..280a712 --- /dev/null +++ b/.changeset/empty-kings-compete.md @@ -0,0 +1,5 @@ +--- +'hoofd': minor +--- + +Make `useLink` concurrent safe by only re-using `link` tags carrying `data-hoofd="1"`, these would come from hydration as hoofd will add these to the static export diff --git a/__tests__/ssr.test.tsx b/__tests__/ssr.test.tsx index c8fe6a0..727d705 100644 --- a/__tests__/ssr.test.tsx +++ b/__tests__/ssr.test.tsx @@ -55,7 +55,9 @@ describe('ssr', () => { expect(lang).toEqual('nl'); expect(title).toEqual('hi'); expect(metas).toEqual([{ content: 'hi', property: 'fb:admins' }]); - expect(links).toEqual([{ rel: 'stylesheet', href: 'x' }]); + expect(links).toEqual([ + { 'data-hoofd': '1', rel: 'stylesheet', href: 'x' }, + ]); expect(scripts).toEqual([ { crossorigin: 'anonymous', @@ -145,8 +147,8 @@ describe('ssr', () => { expect(title).toEqual('bye'); expect(metas).toEqual([{ content: 'bye', property: 'fb:admins' }]); expect(links).toEqual([ - { rel: 'stylesheet', href: 'x' }, - { rel: 'stylesheet', href: 'y' }, + { 'data-hoofd': '1', rel: 'stylesheet', href: 'x' }, + { 'data-hoofd': '1', rel: 'stylesheet', href: 'y' }, ]); expect(scripts).toEqual([ { diff --git a/src/dispatcher/index.ts b/src/dispatcher/index.ts index c83a454..c5f913d 100644 --- a/src/dispatcher/index.ts +++ b/src/dispatcher/index.ts @@ -269,7 +269,7 @@ export const createDispatcher = () => { return { lang, title, - links, + links: links.map((x) => ({ ...x, ['data-hoofd']: '1' })), scripts, metas: metas.map((meta) => meta.keyword === 'charset' diff --git a/src/hooks/useLink.ts b/src/hooks/useLink.ts index 0360981..c277e95 100644 --- a/src/hooks/useLink.ts +++ b/src/hooks/useLink.ts @@ -16,9 +16,9 @@ export interface LinkOptions { export const useLink = (options: LinkOptions) => { const dispatcher = useContext(DispatcherContext); + const hasMounted = useRef(false); const node = useRef(); - const originalOptions = useRef(); if (isServerSide && !hasMounted.current) { dispatcher._addToQueue(LINK, options as any); @@ -39,14 +39,16 @@ export const useLink = (options: LinkOptions) => { options.crossorigin, options.type, options.hreflang, + options.sizes, ]); useEffect(() => { hasMounted.current = true; const preExistingElements = document.querySelectorAll( - `link[rel="${options.rel}"]` + `link[data-hoofd="1"]` ); + // We should be able to recover from SSR like this preExistingElements.forEach((x) => { let found = true; Object.keys(options).forEach((key) => { @@ -61,13 +63,7 @@ export const useLink = (options: LinkOptions) => { } }); - if (node.current) { - originalOptions.current = Object.keys(options).reduce((acc, key) => { - // @ts-ignore - acc[key] = node.current!.getAttribute(key); - return acc; - }, {} as LinkOptions); - } else { + if (!node.current) { node.current = document.createElement('link'); Object.keys(options).forEach((key) => { // @ts-ignore @@ -78,16 +74,9 @@ export const useLink = (options: LinkOptions) => { return () => { hasMounted.current = false; - if (originalOptions.current) { - Object.keys(originalOptions.current).forEach((key) => { - (node.current as Element).setAttribute( - key, - // @ts-ignore - originalOptions.current[key] - ); - }); - } else { + if (node.current) { document.head.removeChild(node.current as Element); + node.current = undefined; } }; }, []);