A React hook for unwrapping Promise values into React state. Handles loading, success, and error states automatically.
function usePromise<T>(
promiseFactory: () => Promise<T>,
inputs: Array<any>,
initialValue: T
): [T | null, any]
| Parameter | Type | Description |
|---|---|---|
| promiseFactory | () => Promise<T> | Factory function that returns a Promise |
| inputs | Array<any> | Dependency array (like useEffect deps) |
| initialValue | T | Value to use before the Promise resolves |
A tuple with two elements:
| Index | Type | Description |
|---|---|---|
| 0 | T | null | The resolved value, or initialValue while loading |
| 1 | any | Error if the Promise rejected, otherwise null |
import { usePromise } from "aidos-ui";
function UserProfile({ userId }) {
const [user, error] = usePromise(
() => fetchUser(userId),
[userId],
null
);
if (error) {
return <ErrorMessage error={error} />;
}
if (!user) {
return <LoadingSpinner />;
}
return <div>{user.name}</div>;
}
Provide an initial value to show immediately while loading:
function Counter() {
const [count, error] = usePromise(
() => fetchCurrentCount(),
[],
0 // Show 0 while loading
);
return <span>{count}</span>;
}
The Promise re-executes when dependencies change:
function SearchResults({ query }) {
const [results, error] = usePromise(
() => searchAPI(query),
[query], // Refetch when query changes
[]
);
return (
<ul>
{results.map((result) => (
<li key={result.id}>{result.title}</li>
))}
</ul>
);
}
function DataDisplay() {
const [data, error] = usePromise(
() => fetchData(),
[],
null
);
if (error) {
return (
<Card>
<Span color="negative">Error: {error.message}</Span>
<Button onClick={() => window.location.reload()}>
Retry
</Button>
</Card>
);
}
if (!data) {
return <Spinner />;
}
return <DataTable data={data} />;
}
Use multiple usePromise hooks for parallel data fetching:
function Dashboard() {
const [stats, statsError] = usePromise(
() => fetchStats(),
[],
null
);
const [notifications, notifError] = usePromise(
() => fetchNotifications(),
[],
[]
);
const [user, userError] = usePromise(
() => fetchCurrentUser(),
[],
null
);
const isLoading = !stats || !user;
const hasError = statsError || notifError || userError;
if (hasError) {
return <ErrorPage />;
}
if (isLoading) {
return <LoadingSkeleton />;
}
return (
<Column gap="large">
<StatsWidget stats={stats} />
<NotificationList notifications={notifications} />
<UserGreeting user={user} />
</Column>
);
}
interface User {
id: string;
name: string;
email: string;
}
function UserCard({ userId }: { userId: string }) {
const [user, error] = usePromise<User | null>(
() => fetchUser(userId),
[userId],
null
);
// user is typed as User | null
if (user) {
return <span>{user.name}</span>;
}
return <span>Loading...</span>;
}
Use dependencies to control when fetching occurs:
function OptionalData({ shouldFetch, id }) {
const [data, error] = usePromise(
() => shouldFetch ? fetchData(id) : Promise.resolve(null),
[shouldFetch, id],
null
);
// ...
}
// ❌ Manual state management
const [data, setData] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
fetchData()
.then(setData)
.catch(setError);
}, []);
// ✅ usePromise - cleaner
const [data, error] = usePromise(() => fetchData(), [], null);
usePromise is simpler but lacks:
Use usePromise for simple cases; consider React Query or SWR for complex data fetching needs.
Use meaningful initial values - Choose values that make sense for your UI (empty array for lists, null for objects)
Handle all states - Always check for error and loading states
Keep factories stable - Avoid creating new functions on each render:
// ❌ Creates new function each render
const [data] = usePromise(() => fetch(`/api/${id}`), [id], null);
// ✅ Factory only changes when id changes
const [data] = usePromise(
() => fetchById(id),
[id],
null
);