Skip to content

Instantly share code, notes, and snippets.

@saitonakamura
Last active February 10, 2020 16:59
Show Gist options
  • Save saitonakamura/2f6b6af28b3412af77062946cb1aadf0 to your computer and use it in GitHub Desktop.
Save saitonakamura/2f6b6af28b3412af77062946cb1aadf0 to your computer and use it in GitHub Desktop.
/* eslint-disable no-console */
import React, {
useContext,
useEffect,
useCallback,
useMemo,
useRef,
} from 'react';
type LockId = number & { readonly __brand: unique symbol };
type IProps = {
fallback: React.ElementType;
noWarn?: boolean | string;
};
type ISeoContextValue = {
canRenderH1: (lockId: LockId | undefined) => boolean;
tryAcquireH1Lock: () => LockId;
releaseH1Lock: (lockId: LockId | undefined) => void;
};
const SeoContext = React.createContext<ISeoContextValue>({
canRenderH1: () => false,
tryAcquireH1Lock: () => 0 as LockId,
releaseH1Lock: () => {},
});
export const SeoH1: React.FC<IProps> = ({
children,
fallback,
noWarn,
...rest
}) => {
const {
canRenderH1,
tryAcquireH1Lock,
releaseH1Lock,
} = useContext(SeoContext);
const h1LockIdRef = useRef<LockId | undefined>(undefined);
if (h1LockIdRef.current === undefined) {
h1LockIdRef.current = tryAcquireH1Lock();
}
useEffect(() => () => releaseH1Lock(h1LockIdRef.current));
if (canRenderH1(h1LockIdRef.current)) {
if (!noWarn) {
// eslint-disable-next-line no-console
console.warn(
'More than one H1 tried to render, check if its expected and disable logging with noWarn in this place'
);
}
return <h1 {...rest}>{children}</h1>;
}
return React.createElement(fallback, rest, children);
};
export const SeoContextProvider: React.FC<{ debug?: boolean }> = ({ children, debug }) => {
const nextLockId = useRef(1 as LockId);
const lockIdRef = useRef<LockId | undefined>(undefined);
const consumerForceRenderRef = useRef(0);
const tryAcquireH1Lock = useCallback(() => {
const lockId = nextLockId.current;
if (debug) console.debug(`Trying to acquire H1 lock, id ${lockId}`);
if (lockIdRef.current === undefined) {
lockIdRef.current = lockId;
if (debug) console.debug(`Successfully acquired H1 lock, id ${lockId}`);
}
nextLockId.current++;
return lockId;
}, []);
const releaseH1Lock = useCallback((lockId: LockId) => {
if (debug) console.debug(`Trying to release H1 lock, id ${lockId}`);
if (lockId && lockIdRef.current === lockId) {
lockIdRef.current = undefined;
consumerForceRenderRef.current += 1;
if (debug) console.debug(`Lock is owned by this lock id ${lockId}, successfully released`);
}
}, []);
const canRenderH1 = useCallback(
(lockId: LockId | undefined) => !!lockId && lockId === lockIdRef.current,
[]
);
const seoContextValue = useMemo<ISeoContextValue>(
() => ({
canRenderH1,
tryAcquireH1Lock,
releaseH1Lock,
}),
[
canRenderH1,
tryAcquireH1Lock,
releaseH1Lock,
consumerForceRenderRef.current,
]
);
return (
<SeoContext.Provider value={seoContextValue}>
{children}
</SeoContext.Provider>
);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment