React components for clean, declarative conditional rendering with async support
—
Promise-based conditional rendering with loading states, error handling, and automatic promise cancellation. Enables clean handling of async operations within conditional rendering.
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>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>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>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>
);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>
);
};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>
);
};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>
);
};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