Expert Patterns — Series 2

Preview — 3 of 10 questions

WidgetList (a Server Component doing a database query) is passed as children into ClientSidebar (a Client Component). Does WidgetList itself need 'use client' just because it ends up rendered inside a client component's tree?

javascript
// app/Layout.jsx — Server Component (no directive)
import ClientSidebar from './ClientSidebar';

async function Layout() {
  const data = await db.query('SELECT * FROM widgets');
  return (
    <ClientSidebar>
      <WidgetList data={data} /> {/* WidgetList is ALSO a Server Component */}
    </ClientSidebar>
  );
}
AYes — any component that appears anywhere inside a Client Component's rendered output must also have 'use client', regardless of how it got there
BNo — WidgetList is rendered (including its database query) entirely on the server, by the Server Component Layout, before the resulting output is handed to ClientSidebar as an opaque children value; ClientSidebar doesn't re-render or re-execute WidgetList on the client, it just places the already-rendered content wherever {children} appears in its own client-side output — this "slot" composition is exactly what lets Server Components stay server-only even when they end up visually nested inside client-interactive UI
CThis throws a build error, since Server Components cannot be passed as props to Client Components under any circumstances
DWidgetList runs twice — once on the server (discarded) and once on the client (the version actually used) — to satisfy React's consistency requirements

Why does structuring ButtonProps as a union of two mutually exclusive shapes (rather than one flat interface with both href? and onClick? as optional) let TypeScript catch the last, invalid usage at compile time?

javascript
type ButtonProps =
  | { variant: 'link'; href: string; onClick?: never }
  | { variant: 'action'; onClick: () => void; href?: never };

function Button(props: ButtonProps) {
  if (props.variant === 'link') return <a href={props.href}>{/* ... */}</a>;
  return <button onClick={props.onClick}>{/* ... */}</button>;
}

// Valid:
<Button variant="link" href="/profile" />
<Button variant="action" onClick={() => {}} />
// Invalid — TypeScript error:
<Button variant="link" href="/profile" onClick={() => {}} />
AA flat interface with both fields optional would catch this too — unions provide no additional type safety over that simpler approach
BTypeScript only supports discriminated unions for exactly two variants — a third variant value would require an entirely different typing approach
CThis requires a runtime library (like Zod) to enforce — TypeScript's own type system cannot express "these two props are mutually exclusive" on its own
DWith a flat interface (href?: string; onClick?: () => void), TypeScript would happily accept a Button with both href and onClick set (or neither) — nothing in that shape says they're mutually exclusive; the discriminated union instead says "this value is either the link shape (which explicitly forbids onClick via onClick?: never) or the action shape (which explicitly forbids href)" — TypeScript's control-flow narrowing based on the variant discriminant, combined with the never fields, is what makes passing both simultaneously a genuine, compile-time type error, not just a convention documented in a comment

Given both patterns eventually display the same data, what's the actual timing difference render-as-you-fetch is named for, and why does it matter for perceived load time?

javascript
// Fetch-on-render (older pattern): the fetch only STARTS once ProfilePage
// has already rendered and its useEffect has run — request initiation
// is delayed until after the component mounts.
function ProfilePage({ userId }) {
  const [user, setUser] = useState(null);
  useEffect(() => { fetchUser(userId).then(setUser); }, [userId]);
  return user ? <Profile user={user} /> : <Spinner />;
}

// Render-as-you-fetch: the fetch is initiated BEFORE/DURING render,
// at the earliest possible moment (e.g. in a route loader or a resource
// created outside the component), with Suspense reading its eventual result.
function ProfilePage({ userResource }) {
  const user = userResource.read(); // Suspends until data is ready
  return <Profile user={user} />;
}
AIn fetch-on-render, the network request only starts after ProfilePage has already rendered once and its effect has run — there's a render-then-fetch sequence, meaning the request doesn't begin until that first render/commit has already happened; render-as-you-fetch initiates the actual network request as early as possible — often before the component doing the eventual rendering has even started rendering (e.g., triggered by a route transition, in parallel with beginning to render other parts of the page) — so the request is already in flight while other rendering work happens concurrently, rather than being delayed behind an initial render cycle
BRender-as-you-fetch is purely a caching optimization — the actual network request timing is identical between both patterns
CFetch-on-render is strictly faster in every case, since it avoids the overhead of Suspense's fallback mechanism entirely
DRender-as-you-fetch requires the data source to be a GraphQL API specifically — it cannot be used with plain REST endpoints

Sign up free to play

Answer all 10 questions (7 more), see explanations for every answer, and track your score.