Skip to main content
The ClientGate component ensures content is rendered only after JavaScript has loaded client-side, with optional fallback support for server-side rendering or no-JS environments.

When to Use

  • Render components that depend on browser APIs (window, document, etc.)
  • Prevent hydration mismatches from client-only features
  • Show skeleton loaders during hydration
  • Progressively enhance server-rendered content
  • Handle components that require JavaScript to function

Basic Usage

import { ClientGate } from "@zayne-labs/ui-react/common/client-gate";

function App() {
  return (
    <ClientGate fallback={<div>Loading...</div>}>
      <InteractiveChart />
    </ClientGate>
  );
}

Component API

children
React.ReactNode | (() => React.ReactNode)
required
Content to render after client-side hydration. Can be a render function for deferred initialization
fallback
React.ReactNode
Content to show during SSR or before hydration. Should match the dimensions of the hydrated content to avoid layout shift
You are encouraged to add a fallback that matches the dimensions of the client-rendered children to avoid content layout shift.

Examples

Browser API-Dependent Component

import { ClientGate } from "@zayne-labs/ui-react/common/client-gate";

function GeolocationDisplay() {
  return (
    <ClientGate fallback={<div>Detecting location...</div>}>
      {() => {
        const [location, setLocation] = useState(null);

        useEffect(() => {
          navigator.geolocation.getCurrentPosition((position) => {
            setLocation({
              lat: position.coords.latitude,
              lng: position.coords.longitude,
            });
          });
        }, []);

        return location ? (
          <div>
            Lat: {location.lat}, Lng: {location.lng}
          </div>
        ) : (
          <div>Loading location...</div>
        );
      }}
    </ClientGate>
  );
}

Chart with Skeleton Fallback

import { ClientGate } from "@zayne-labs/ui-react/common/client-gate";

function ChartSkeleton() {
  return (
    <div className="w-full h-[400px] bg-gray-100 animate-pulse rounded-lg" />
  );
}

function AnalyticsDashboard() {
  return (
    <div>
      <h1>Sales Analytics</h1>
      <ClientGate fallback={<ChartSkeleton />}>
        {() => <RechartsComponent data={salesData} />}
      </ClientGate>
    </div>
  );
}

Third-Party Widget

import { ClientGate } from "@zayne-labs/ui-react/common/client-gate";

function SocialEmbed() {
  return (
    <ClientGate
      fallback={
        <div className="w-full h-[500px] flex items-center justify-center bg-gray-50">
          <p>Loading embed...</p>
        </div>
      }
    >
      {() => (
        <div>
          <TwitterEmbed tweetId="123456789" />
        </div>
      )}
    </ClientGate>
  );
}

Window Size Dependent Component

import { ClientGate } from "@zayne-labs/ui-react/common/client-gate";
import { useWindowSize } from "@zayne-labs/toolkit-react";

function ResponsiveComponent() {
  return (
    <ClientGate fallback={<div className="h-screen">Loading...</div>}>
      {() => {
        const { width } = useWindowSize();

        return (
          <div>
            {width > 768 ? (
              <DesktopLayout />
            ) : (
              <MobileLayout />
            )}
          </div>
        );
      }}
    </ClientGate>
  );
}

Comparison to Native Patterns

Traditional Approach

function ChartComponent() {
  const [isClient, setIsClient] = useState(false);

  useEffect(() => {
    setIsClient(true);
  }, []);

  if (!isClient) {
    return <div>Loading chart...</div>;
  }

  return <Chart />;
}

With ClientGate

function ChartComponent() {
  return (
    <ClientGate fallback={<div>Loading chart...</div>}>
      <Chart />
    </ClientGate>
  );
}

Common Use Cases

Progressive Enhancement

import { ClientGate } from "@zayne-labs/ui-react/common/client-gate";

function ArticlePage({ article }) {
  return (
    <article>
      <h1>{article.title}</h1>
      <div>{article.content}</div>

      {/* Enhanced features only on client */}
      <ClientGate fallback={null}>
        <ShareButtons />
        <CommentSection />
        <RelatedArticles />
      </ClientGate>
    </article>
  );
}

Preventing Hydration Mismatches

import { ClientGate } from "@zayne-labs/ui-react/common/client-gate";

function UserGreeting() {
  return (
    <ClientGate fallback={<div>Welcome!</div>}>
      {() => {
        const hour = new Date().getHours();
        const greeting =
          hour < 12 ? "Good morning"
          : hour < 18 ? "Good afternoon"
          : "Good evening";

        return <div>{greeting}!</div>;
      }}
    </ClientGate>
  );
}
Avoid using ClientGate for content that should be SEO-friendly. The fallback is what search engines will see.

Implementation Details

The component uses the useIsHydrated hook internally to detect when the client has hydrated:
function ClientGate(props: ClientGateProps) {
  const { children, fallback } = props;
  const isHydrated = useIsHydrated();
  const resolvedChildren = () => (isFunction(children) ? children() : children);

  return isHydrated ? resolvedChildren() : fallback;
}

Build docs developers (and LLMs) love