npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

keep-render

v3.0.0

Published

keep-render is a React package that simplifies conditional rendering. It provides components for handling loading states, and for rendering content based on conditions.

Readme

keep-render

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.

Installation

npm install keep-render

Quick Start

import { 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>
  );
}

Examples

Standalone condition

Render.When can be used outside of Render for simple inline conditions.

<Render.When isTrue={user.isAdmin}>
  <AdminBadge />
</Render.When>

Typed API response with Render.Data

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>

Error handling

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>

Nested conditions

<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>

Nested permission checks

<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>

Complex Examples

API table with error, empty, data, and pagination

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>
  );
}

Multi-step wizard form

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>
  );
}

Dashboard with role-based access control

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>
  );
}

CRUD table with inline editing

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>
  );
}

Real-time notification feed

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>
  );
}

Before / After Comparison

// ❌ 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>

API Reference

<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

<Render.When>

Renders children when isTrue is truthy. Can be used standalone outside Render.

| Prop | Type | Description | |------|------|-------------| | isTrue | boolean | Condition to evaluate |

<Render.Data>

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 |

<Render.Error>

Renders when parent Render has isError={true}.

<Render.Else>

Fallback content when no Render.When or Render.Data matches.

<Render.Loading>

Full content replacement when parent Render has isLoading={true}.

Context

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.

License

MIT