CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-react-if

React components for clean, declarative conditional rendering with async support

Pending
Overview
Eval results
Files

async-conditions.mddocs/

Asynchronous Conditions

Promise-based conditional rendering with loading states, error handling, and automatic promise cancellation. Enables clean handling of async operations within conditional rendering.

Capabilities

Async If Component

When the If component receives a Promise as its condition, it automatically handles the Promise lifecycle with loading, success, and error states.

/**
 * When condition is a Promise:
 * - Renders Fallback block while Promise is pending
 * - Renders Then block when Promise resolves
 * - Renders Else block when Promise rejects
 * - Supports promise cancellation via keepAlive prop
 */
const If: FC<{
  condition: Promise<any>;
  keepAlive?: boolean;
  children: ReactNode;
}>;

Usage Examples:

import { If, Then, Else, Fallback } from "react-if";

// Basic async condition
<If condition={fetchUserData()}>
  <Fallback>
    <LoadingSpinner />
  </Fallback>
  <Then>
    {(userData) => <UserProfile user={userData} />}
  </Then>
  <Else>
    {(error) => <ErrorMessage error={error} />}
  </Else>
</If>

// With keepAlive to prevent cancellation
<If condition={fetchCriticalData()} keepAlive={true}>
  <Fallback>
    <div className="loading">Loading critical data...</div>
  </Fallback>
  <Then>
    {(data) => <CriticalDataView data={data} />}
  </Then>
  <Else>
    {(error) => (
      <div className="error">
        Failed to load: {error.message}
        <button onClick={retry}>Retry</button>
      </div>
    )}
  </Else>
</If>

Fallback Component

Loading state component that renders while a Promise condition is pending.

/**
 * Must contain only a single child, which it renders as-is.
 * Should not be used outside of an <If /> block whose condition prop is a promise.
 * Renders while the parent Promise condition is pending.
 */
const Fallback: FC<{
  children?: ReactNode | (() => JSX.Element);
}>;

Usage Examples:

// Simple loading indicator
<Fallback>
  <div className="spinner">Loading...</div>
</Fallback>

// Function children for dynamic loading states
<Fallback>
  {() => <AdvancedLoadingSpinner progress={loadingProgress} />}
</Fallback>

// Rich loading experience
<Fallback>
  <div className="loading-container">
    <LoadingSpinner />
    <p>Fetching your data...</p>
    <ProgressBar />
  </div>
</Fallback>

Promise Result Handling

Then and Else blocks receive the Promise result as their first parameter when used with async conditions.

/**
 * Function children for Then/Else blocks receive:
 * @param returnValue - The resolved value (Then) or rejection error (Else)
 * @param promiseHistory - Array of all promises passed to If
 * @param cancellablePromise - The specific promise that triggered this render
 */
type AsyncChildrenFunction = (
  returnValue: any,
  promiseHistory: CancellablePromise[],
  cancellablePromise: ExtendablePromise<any>
) => JSX.Element;

/**
 * Enhanced Then component for async conditions - when used with Promise conditions,
 * function children receive the resolved value as the first parameter
 */
interface AsyncThenProps {
  children?: ReactNode | AsyncChildrenFunction;
}

/**
 * Enhanced Else component for async conditions - when used with Promise conditions,
 * function children receive the rejection error as the first parameter
 */
interface AsyncElseProps {
  children?: ReactNode | AsyncChildrenFunction;
}

**Usage Examples:**

```typescript
// Access resolved data in Then block
<Then>
  {(userData, history, promise) => (
    <div>
      <h1>Welcome, {userData.name}!</h1>
      <p>Account created: {userData.createdAt}</p>
      <small>Request ID: {promise.requestId}</small>
    </div>
  )}
</Then>

// Handle errors in Else block
<Else>
  {(error, history, promise) => (
    <div className="error">
      <h3>Error loading data</h3>
      <p>{error.message}</p>
      <details>
        <summary>Technical Details</summary>
        <pre>{JSON.stringify({ error, promiseCount: history.length }, null, 2)}</pre>
      </details>
      <button onClick={() => retry(promise)}>Retry</button>
    </div>
  )}
</Else>

Advanced Usage Patterns

Promise Cancellation Control

const DataComponent = ({ shouldKeepAlive, dataPromise }) => (
  <If condition={dataPromise} keepAlive={shouldKeepAlive}>
    <Fallback>
      <div>
        Loading data...
        {!shouldKeepAlive && <small>Will cancel if component unmounts</small>}
      </div>
    </Fallback>
    <Then>
      {(data) => <DataDisplay data={data} />}
    </Then>
    <Else>
      {(error) => <ErrorDisplay error={error} />}
    </Else>
  </If>
);

Multiple Async Operations

const MultiDataComponent = () => {
  const [currentPromise, setCurrentPromise] = useState(null);
  
  const loadUser = () => setCurrentPromise(fetchUser());
  const loadPosts = () => setCurrentPromise(fetchPosts());
  
  return (
    <div>
      <button onClick={loadUser}>Load User</button>
      <button onClick={loadPosts}>Load Posts</button>
      
      <If condition={currentPromise}>
        <Fallback>
          <LoadingIndicator />
        </Fallback>
        <Then>
          {(data, history) => (
            <div>
              <DataDisplay data={data} />
              <p>Total requests: {history.length}</p>
            </div>
          )}
        </Then>
        <Else>
          {(error) => <ErrorMessage error={error} />}
        </Else>
      </If>
    </div>
  );
};

Promise Chaining and History

const ChainedDataLoader = ({ userId }) => {
  const loadUserAndPosts = async () => {
    const user = await fetchUser(userId);
    const posts = await fetchUserPosts(user.id);
    return { user, posts };
  };

  return (
    <If condition={loadUserAndPosts()}>
      <Fallback>
        <div>Loading user and posts...</div>
      </Fallback>
      <Then>
        {(data, history, currentPromise) => (
          <div>
            <UserProfile user={data.user} />
            <PostsList posts={data.posts} />
            <DebugInfo>
              Promise history: {history.length} total requests
              Current promise: {currentPromise.constructor.name}
            </DebugInfo>
          </div>
        )}
      </Then>
      <Else>
        {(error, history) => (
          <div>
            <ErrorDisplay error={error} />
            <RetryButton 
              onRetry={() => window.location.reload()}
              attempts={history.length}
            />
          </div>
        )}
      </Else>
    </If>
  );
};

Conditional Promise Execution

const ConditionalDataLoader = ({ shouldLoad, params }) => {
  // Only create promise when needed
  const dataPromise = shouldLoad ? fetchData(params) : null;
  
  return (
    <div>
      {dataPromise ? (
        <If condition={dataPromise}>
          <Fallback>Loading...</Fallback>
          <Then>{(data) => <DataView data={data} />}</Then>
          <Else>{(error) => <ErrorView error={error} />}</Else>
        </If>
      ) : (
        <div>Click load to fetch data</div>
      )}
    </div>
  );
};

Type Definitions

interface ExtendablePromise<T> extends Promise<T> {
  [index: string]: any;
}

interface CancellablePromise {
  promise: ExtendablePromise<any>;
  cancel: () => void;
}

interface AsyncSupportProps {
  /**
   * - False (default): promises are cancelled before each unmount
   * - True: promises can be fulfilled even after a
   * component unmount or a change to promise prop
   */
  keepAlive?: boolean;
}

type ComponentWithConditionPropsAsyncSupport = {
  condition: Promise<any>;
  keepAlive?: boolean;
  children: ReactNode;
};

type AsyncChildrenFunction = (
  returnValue: any,
  promiseHistory: CancellablePromise[],
  cancellablePromise: ExtendablePromise<any>
) => JSX.Element;

Install with Tessl CLI

npx tessl i tessl/npm-react-if

docs

async-conditions.md

basic-conditions.md

index.md

shorthand.md

switch-case.md

tile.json