Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
React SSR hydration boundary
import { cloneElement, FC, ReactNode, useRef } from 'react';
import { getAppHasHydrated } from '../MyApp';
export interface HydrateScopeProps {
hydrate: boolean;
render: () => JSX.Element | ReactNode;
}
const IS_SERVER = !process.browser;
const SKIP = {};
const HydrateScope: FC<HydrateScopeProps> = ({ hydrate, render }) => {
const hasHydratedRef = useRef(false);
// If the app hasn't hydrated yet and this component is being rendered
// then we know that it was rendered on the server.
const wasServerRendered = useRef(!getAppHasHydrated()).current;
const shouldHydrate =
// always deep-render on server
IS_SERVER ||
// if this component instance has deep-rendered before it must continue to do so
hasHydratedRef.current ||
// if the hydrate prop is true we always deep render
hydrate ||
// If the `hydrate` prop is false but we KNOW this component wasn't
// server-rendered then we MUST render it. When components are INITIALLY
// rendered on the client, the full 'deep' render must happen else
// the component will be empty.
!wasServerRendered;
// child must be plain html element otherwise doesn't work
const child = <div>{render()}</div>;
// once hydrated all subsequence renders must hydrate
if (shouldHydrate) {
hasHydratedRef.current = true;
console.log('hydrate');
return child;
}
console.log('skip hydrate');
// @ts-ignore
return cloneElement(child, {
dangerouslySetInnerHTML: SKIP,
children: null,
});
};
export default HydrateScope;
export const getAppHasHydrated = () => getAppHasHydrated.v;
getAppHasHydrated.v = false;
const MyApp = () => {
// when the app has mounted we know hydration is complete
useEffect(() => {
getAppHasHydrated.v = true;
}, []);
return <div></div>
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment