A React package that simplifies conditional rendering. It provides declarative components for handling loading, error, and data states — making your JSX more readable and maintainable with less boilerplate.
npm install keep-renderimport { Render } from "keep-render";
function App() {
const isLoading = false;
const isError = false;
const hasData = true;
return (
<Render isLoading={isLoading} isError={isError}>
<Render.Loading>
<div>Loading...</div>
</Render.Loading>
<Render.Error>
<div>Something went wrong</div>
</Render.Error>
<Render.When isTrue={hasData}>
<div>Data is available</div>
</Render.When>
<Render.Else>
<div>No data available</div>
</Render.Else>
</Render>
);
}Render.When can be used outside of Render for simple inline conditions.
<Render.When isTrue={user.isAdmin}>
<AdminBadge />
</Render.When>Render.Data extracts response.data and passes it to a callback. The second argument tells you whether data is still loading.
type User = { id: string; name: string; email: string };
function UserHeader({
response,
isLoading,
}: {
response?: { data?: User };
isLoading: boolean;
}) {
return (
<Render isLoading={isLoading}>
<Render.Data response={response}>
{(user, loading) =>
loading ? <UserSkeleton /> : <UserCard user={user} />
}
</Render.Data>
<Render.Else>No user found</Render.Else>
<Render.Loading>
<UserSkeleton />
</Render.Loading>
</Render>
);
}If you don't need the response value, pass normal JSX children:
<Render.Data response={response}>
<UserSummary />
</Render.Data>Render.Error renders when the parent Render receives isError={true}. Error has priority over Render.Data, Render.When, and Render.Else. Loading still takes top priority.
<Render isLoading={isLoading} isError={isError}>
<Render.Error>
<div role="alert">Could not load user.</div>
</Render.Error>
<Render.Data response={response}>
{(user) => <UserCard user={user} />}
</Render.Data>
<Render.Else>No user found</Render.Else>
</Render><Render>
<Render.When isTrue={condition1}>
<div>Condition 1 met</div>
<Render>
<Render.When isTrue={condition2}>
<div>Condition 2 also met</div>
</Render.When>
<Render.Else>Condition 2 not met</Render.Else>
</Render>
</Render.When>
<Render.Else>
<div>No conditions met</div>
</Render.Else>
</Render><Render>
<Render.Data response={{ data: session?.user }}>
{(user) => (
<Render>
<Render.When isTrue={user.role === "admin"}>
<AdminPanel user={user} />
</Render.When>
<Render.Else>
<UserPanel user={user} />
</Render.Else>
</Render>
)}
</Render.Data>
<Render.Else>Please sign in.</Render.Else>
</Render>type Product = { id: string; name: string; stock: number };
type ProductResponse = { results: Product[]; total: number };
function ProductTable({
data,
isLoading,
isError,
}: {
data?: ProductResponse;
isLoading: boolean;
isError: boolean;
}) {
return (
<Render isLoading={isLoading}>
<Render.When isTrue={isError}>
<div role="alert">Could not load products.</div>
</Render.When>
<Render.When isTrue={Boolean(data && data.results.length === 0)}>
<div>No products found.</div>
</Render.When>
<Render.Data response={{ data }}>
{(products, loading) => (
<section>
{loading ? (
<div>Loading products...</div>
) : (
<>
<table>
<tbody>
{products.results.map((p) => (
<tr key={p.id}>
<td>{p.name}</td>
<td>{p.stock}</td>
</tr>
))}
</tbody>
</table>
<Render.When isTrue={products.total > 10}>
<Pagination total={products.total} />
</Render.When>
</>
)}
</section>
)}
</Render.Data>
<Render.Else>No data</Render.Else>
</Render>
);
}Handle complex form flows where each step has its own loading, validation, and error states.
type Step = "info" | "address" | "confirm";
function CheckoutWizard({
step,
userResponse,
addressResponse,
orderResponse,
isSubmitting,
submitError,
}: {
step: Step;
userResponse?: { data?: { name: string; email: string } };
addressResponse?: { data?: { city: string; zip: string } };
orderResponse?: { data?: { orderId: string } };
isSubmitting: boolean;
submitError: boolean;
}) {
return (
<Render isLoading={isSubmitting} isError={submitError}>
<Render.Loading>
<div>Processing your order...</div>
</Render.Loading>
<Render.Error>
<div role="alert">Payment failed. Please try again.</div>
</Render.Error>
<Render.When isTrue={step === "info"}>
<Render isLoading={!userResponse}>
<Render.Data response={userResponse}>
{(user, loading) =>
loading ? <FormSkeleton /> : <UserInfoForm defaultValues={user} />
}
</Render.Data>
<Render.Loading>
<FormSkeleton />
</Render.Loading>
</Render>
</Render.When>
<Render.When isTrue={step === "address"}>
<Render.Data response={addressResponse}>
{(address) => <AddressForm defaultValues={address} />}
</Render.Data>
</Render.When>
<Render.When isTrue={step === "confirm"}>
<Render.Data response={orderResponse}>
{(order) => <h2>Order #{order.orderId} confirmed!</h2>}
</Render.Data>
</Render.When>
<Render.Else>
<div>Unknown step</div>
</Render.Else>
</Render>
);
}Combine authentication, role checks, and feature flags in a single declarative tree.
type User = { role: "free" | "pro" | "admin"; name: string };
function AnalyticsDashboard({
session,
analyticsResponse,
isLoading,
isError,
}: {
session?: { data?: User };
analyticsResponse?: { data?: { visits: number; revenue: number } };
isLoading: boolean;
isError: boolean;
}) {
return (
<Render>
<Render.Data response={session}>
{(user) => (
<Render isLoading={isLoading} isError={isError}>
<Render.Loading>
<DashboardSkeleton />
</Render.Loading>
<Render.Error>
<div role="alert">Failed to load analytics.</div>
</Render.Error>
<Render.When isTrue={user.role === "admin"}>
<Render.Data response={analyticsResponse}>
{(stats) => (
<>
<StatsCard title="Visits" value={stats.visits} />
<StatsCard title="Revenue" value={`$${stats.revenue}`} />
<AdminControls />
</>
)}
</Render.Data>
</Render.When>
<Render.When isTrue={user.role === "pro"}>
<Render.Data response={analyticsResponse}>
{(stats) => (
<>
<StatsCard title="Visits" value={stats.visits} />
<StatsCard title="Revenue" value={`$${stats.revenue}`} />
</>
)}
</Render.Data>
</Render.When>
<Render.Else>
<UpgradePrompt currentPlan={user.role} />
</Render.Else>
</Render>
)}
</Render.Data>
<Render.Else>
<LoginPrompt />
</Render.Else>
</Render>
);
}Row-level state management using Render inside a .map() loop.
type Employee = { id: string; name: string; department: string };
function EmployeeTable({
response,
isLoading,
isError,
editingId,
deletingId,
onEdit,
onDelete,
onSave,
}: {
response?: { data?: Employee[] };
isLoading: boolean;
isError: boolean;
editingId: string | null;
deletingId: string | null;
onEdit: (id: string) => void;
onDelete: (id: string) => void;
onSave: (emp: Employee) => void;
}) {
return (
<Render isLoading={isLoading} isError={isError}>
<Render.Loading>
<TableSkeleton rows={5} cols={3} />
</Render.Loading>
<Render.Error>
<div role="alert">Could not load employees.</div>
</Render.Error>
<Render.Data response={response}>
{(employees) => (
<Render>
<Render.When isTrue={employees.length === 0}>
<div>No employees found.</div>
</Render.When>
<Render.Else>
<table>
<tbody>
{employees.map((emp) => (
<tr key={emp.id}>
<Render>
<Render.When isTrue={editingId === emp.id}>
<td><input defaultValue={emp.name} /></td>
<td><input defaultValue={emp.department} /></td>
<td><button onClick={() => onSave(emp)}>Save</button></td>
</Render.When>
<Render.When isTrue={deletingId === emp.id}>
<td colSpan={3}>Deleting...</td>
</Render.When>
<Render.Else>
<td>{emp.name}</td>
<td>{emp.department}</td>
<td>
<button onClick={() => onEdit(emp.id)}>Edit</button>
<button onClick={() => onDelete(emp.id)}>Delete</button>
</td>
</Render.Else>
</Render>
</tr>
))}
</tbody>
</table>
</Render.Else>
</Render>
)}
</Render.Data>
<Render.Else>No data available</Render.Else>
</Render>
);
}Handle WebSocket connection states, empty feeds, and live updates with nested Render inside error UI.
type Notification = { id: string; message: string; read: boolean };
type WsStatus = "connecting" | "connected" | "disconnected" | "error";
function NotificationFeed({
status,
notifications,
}: {
status: WsStatus;
notifications: Notification[];
}) {
const unread = notifications.filter((n) => !n.read);
return (
<Render
isLoading={status === "connecting"}
isError={status === "error" || status === "disconnected"}
>
<Render.Loading>
<div>Connecting to live feed...</div>
</Render.Loading>
<Render.Error>
<div role="alert">
<Render>
<Render.When isTrue={status === "disconnected"}>
<p>Connection lost. Reconnecting...</p>
</Render.When>
<Render.Else>
<p>Failed to connect. Please refresh.</p>
</Render.Else>
</Render>
</div>
</Render.Error>
<Render.When isTrue={notifications.length === 0}>
<div>No notifications yet.</div>
</Render.When>
<Render.When isTrue={notifications.length > 0}>
<>
<Render.When isTrue={unread.length > 0}>
<span className="badge">{unread.length} new</span>
</Render.When>
<ul>
{notifications.map((n) => (
<li key={n.id} className={n.read ? "read" : "unread"}>
{n.message}
</li>
))}
</ul>
</>
</Render.When>
</Render>
);
}// ❌ Traditional ternary nesting
{
isLoading ? (
<Loading />
) : (
<Col span={24}>
{(!isLoading && isError) || data?.data?.results?.length === 0 ? (
<div>Sorry! No Flights Found.</div>
) : (
<>
{data?.data?.results?.map((item, i) => (
<FlightCard Item={item} key={i} />
))}
{(data?.count || 0) > 5 && (
<Pagination total={data?.count || 0} onChange={handlePageChange} />
)}
</>
)}
</Col>
);
}
// ✅ Using keep-render
<Render isLoading={isLoading}>
<Render.When isTrue={isError || data?.data?.results?.length === 0}>
<div>Sorry! No Flights Found.</div>
</Render.When>
<Render.Else>
{data?.data?.results?.map((item, i) => (
<FlightCard Item={item} key={i} />
))}
<Render.When isTrue={(data?.count || 0) > 5}>
<Pagination total={data?.count || 0} onChange={handlePageChange} />
</Render.When>
</Render.Else>
<Render.Loading>
<Loading />
</Render.Loading>
</Render>The root wrapper. Accepts optional isLoading and isError props.
| Prop | Type | Description |
|---|---|---|
isLoading |
boolean |
When true and Render.Loading exists, replaces all content |
isError |
boolean |
When true and Render.Error exists, shows error content |
Priority order: Loading → Error → When/Data matches → Else
Renders children when isTrue is truthy. Can be used standalone outside Render.
| Prop | Type | Description |
|---|---|---|
isTrue |
boolean |
Condition to evaluate |
Extracts response.data and passes it to a render callback.
| Prop | Type | Description |
|---|---|---|
response |
{ data?: T } |
API response object |
children |
(data: T, isLoading: boolean) => ReactNode or ReactNode |
Callback or JSX |
Renders when parent Render has isError={true}.
Fallback content when no Render.When or Render.Data matches.
Full content replacement when parent Render has isLoading={true}.
Render uses React context internally. Render.Else, Render.Error, and Render.Loading must be used within a Render. Render.When works both inside and outside Render.
MIT