30 lines
840 B
TypeScript
30 lines
840 B
TypeScript
"use client";
|
|
|
|
import { useSyncExternalStore, type ReactNode } from "react";
|
|
|
|
const emptySubscribe = () => () => {};
|
|
|
|
/**
|
|
* Renders its children only after hydration on the client. Use this to wrap UI
|
|
* whose initial render is non-deterministic (e.g. seeded with Math.random()),
|
|
* which would otherwise cause a server/client hydration mismatch. The
|
|
* `fallback` is shown during SSR and the first (hydrating) client render.
|
|
*
|
|
* Uses useSyncExternalStore rather than a mount effect so there is no
|
|
* setState-in-effect and the server/client snapshots stay explicit.
|
|
*/
|
|
export function ClientOnly({
|
|
children,
|
|
fallback = null,
|
|
}: {
|
|
children: ReactNode;
|
|
fallback?: ReactNode;
|
|
}) {
|
|
const isServer = useSyncExternalStore(
|
|
emptySubscribe,
|
|
() => false,
|
|
() => true,
|
|
);
|
|
return <>{isServer ? fallback : children}</>;
|
|
}
|