JAVASCRIPT
Manage Async Operations with a useAsyncOperation Hook
Build a useAsyncOperation React hook to elegantly handle the loading, success, and error states of any asynchronous Promise-based function in your components.
import { useState, useEffect, useCallback } => 'react';
function useAsyncOperation(asyncFunction, immediate = false) {
const [status, setStatus] = useState('idle');
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const execute = useCallback(async (...args) => {
setStatus('pending');
setData(null);
setError(null);
try {
const response = await asyncFunction(...args);
setData(response);
setStatus('success');
return response;
} catch (err) {
setError(err);
setStatus('error');
throw err;
}
}, [asyncFunction]);
useEffect(() => {
if (immediate) {
execute();
}
}, [execute, immediate]);
return { execute, status, data, error };
}
/*
// Example usage:
function UserProfile() {
const fetchUserData = useCallback(async (userId) => {
const res = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
if (!res.ok) {
throw new Error(`Failed to fetch user ${userId}: ${res.statusText}`);
}
return res.json();
}, []);
const { execute, status, data, error } = useAsyncOperation(fetchUserData);
const handleLoadUser = () => {
execute(1); // Load user with ID 1
};
if (status === 'pending') return <div>Loading user data...</div>;
if (status === 'error') return <div style={{ color: 'red' }}>Error: {error.message}</div>;
if (status === 'success' && data) {
return (
<div>
<h2>User Profile</h2>
<p>Name: {data.name}</p>
<p>Email: {data.email}</p>
<button onClick={handleLoadUser}>Reload User</button>
</div>
);
}
return <button onClick={handleLoadUser}>Load User 1</button>;
}
*/
How it works: The useAsyncOperation hook streamlines the management of asynchronous processes by providing `status`, `data`, and `error` states. It takes an `asyncFunction` (which should return a Promise) and an `immediate` flag. The `execute` function can be called to run the `asyncFunction`, updating the state based on its resolution or rejection. This pattern centralizes error handling and loading indicators for any Promise-based task, making component logic cleaner and more robust.