← Back to all snippets
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.

Need help integrating this into your project?

Our team of expert developers can help you build your custom application from scratch.

Hire DigitalCodeLabs