Last active
November 17, 2022 09:40
-
-
Save wilsonpage/2528375496bccadbbc6b43f1381d2f94 to your computer and use it in GitHub Desktop.
React SSR hydration boundary
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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