Guard Clause Rendering: Handle Edge Cases Up Front
Use early returns for loading and error states to keep your main render clean.
Guard clause rendering is early returns in your render function: handle the loading and error cases before the main UI, and let the happy path read top to bottom.
Most components I’ve abandoned mid-refactor had this shape. The edge cases are nested inside the main render, so by the time you reach the actual content you’re three ternaries deep.
The Ternary Ladder
The familiar version. Loading, error, and content all fight for the same expression:
function UserProfile({ loading, error, user }) {
return (
<div>
{loading ? (
<p>Loading...</p>
) : error ? (
<p>Something went wrong!</p>
) : (
<>
<h2>{user.name}</h2>
<p>{user.email}</p>
<p>{user.role}</p>
</>
)}
</div>
);
}
function App() {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [user, setUser] = useState({ name: "John Doe", email: "john@example.com", role: "Developer" });
return (
<div>
<h1>Guard Clause Rendering</h1>
<UserProfile loading={loading} error={error} user={user} />
</div>
);
}
export default App;
Return Early, Render Late
Move the loading and error UI into small components and return them before the main render. The happy path ends up flat:
import { useState } from "react";
function LoadingComponent() {
return <p>Loading...</p>;
}
function ErrorComponent() {
return <p>Something went wrong!</p>;
}
function UserProfile({ loading, error, user }) {
if (loading) return <LoadingComponent />;
if (error) return <ErrorComponent />;
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
<p>{user.role}</p>
</div>
);
}
function App() {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [user, setUser] = useState({ name: "John Doe", email: "john@example.com", role: "Developer" });
return (
<div>
<h1>Guard Clause Rendering</h1>
<UserProfile loading={loading} error={error} user={user} />
</div>
);
}
export default App;
Extracting LoadingComponent and ErrorComponent pays off twice: the guards stay one-liners, and the loading and error UI stays consistent across screens that reuse them.
If you find yourself chaining many guards, the component probably wants a status value instead, which is the state machine pattern’s job. Early returns also pair well with Suspense and error boundaries, which each take over one of the guards for you.