← Back to all hooks

useAsync

A generic wrapper for any async function or Promise. Handle loading, error, and data states with ease.

Live Demo

Try different async operations: manual execution, immediate loading, error handling, and state reset.

🎯 Manual Execution

Execute async function on demand

Click "Fetch User" to load data

⚡ Immediate Execution

Auto-execute on mount with immediate: true

🐌 Slow Operation

Simulate long-running async operations

Click to start

❌ Error Handling

Gracefully handle async errors

Click to trigger an error

🔢 Calculation with Reset

Calculate factorial and reset state

Enter a number and click Calculate

💡 Features Demonstrated:

  • Manual execution with execute() function
  • Immediate execution on mount
  • Loading, error, and data states
  • Error handling and recovery
  • Reset functionality to clear state
  • Works with any async function or Promise

Code Examples

Basic Usage

import { useAsync } from "@uiblock/hooks";

const fetchUser = async (userId: number) => {
  const response = await fetch(`/api/users/${userId}`);
  return response.json();
};

export default function UserProfile() {
  const { data, loading, error, execute } = useAsync(fetchUser);

  return (
    <div>
      <button onClick={() => execute(1)} disabled={loading}>
        Load User
      </button>
      
      {loading && <p>Loading...</p>}
      {error && <p>Error: {error.message}</p>}
      {data && <div>{data.name}</div>}
    </div>
  );
}

Immediate Execution

import { useAsync } from "@uiblock/hooks";

const fetchInitialData = async () => {
  const response = await fetch('/api/initial-data');
  return response.json();
};

export default function Dashboard() {
  const { data, loading, error } = useAsync(fetchInitialData, {
    immediate: true // Execute on mount
  });

  if (loading) return <div>Loading dashboard...</div>;
  if (error) return <div>Error: {error.message}</div>;
  
  return <div>{/* Render dashboard with data */}</div>;
}

With Callbacks

import { useAsync } from "@uiblock/hooks";

const saveData = async (data: any) => {
  const response = await fetch('/api/save', {
    method: 'POST',
    body: JSON.stringify(data)
  });
  return response.json();
};

export default function SaveForm() {
  const { loading, execute } = useAsync(saveData, {
    onSuccess: (data) => {
      console.log('Saved successfully:', data);
      alert('Data saved!');
    },
    onError: (error) => {
      console.error('Save failed:', error);
      alert('Failed to save');
    }
  });

  const handleSubmit = (formData: any) => {
    execute(formData);
  };

  return (
    <form onSubmit={(e) => {
      e.preventDefault();
      handleSubmit({ /* form data */ });
    }}>
      <button type="submit" disabled={loading}>
        {loading ? 'Saving...' : 'Save'}
      </button>
    </form>
  );
}

Reset State

import { useAsync } from "@uiblock/hooks";

const searchUsers = async (query: string) => {
  const response = await fetch(`/api/search?q=${query}`);
  return response.json();
};

export default function SearchBox() {
  const { data, loading, execute, reset } = useAsync(searchUsers);

  const handleSearch = (query: string) => {
    if (query) {
      execute(query);
    } else {
      reset(); // Clear results when query is empty
    }
  };

  return (
    <div>
      <input 
        onChange={(e) => handleSearch(e.target.value)}
        placeholder="Search users..."
      />
      <button onClick={reset}>Clear</button>
      
      {loading && <p>Searching...</p>}
      {data && <div>{/* Render results */}</div>}
    </div>
  );
}

Multiple Async Operations

import { useAsync } from "@uiblock/hooks";

const fetchUser = async (id: number) => { /* ... */ };
const fetchPosts = async (userId: number) => { /* ... */ };

export default function UserDashboard() {
  const { 
    data: user, 
    loading: userLoading, 
    execute: loadUser 
  } = useAsync(fetchUser);
  
  const { 
    data: posts, 
    loading: postsLoading, 
    execute: loadPosts 
  } = useAsync(fetchPosts);

  const loadUserData = async (userId: number) => {
    await loadUser(userId);
    await loadPosts(userId);
  };

  return (
    <div>
      <button onClick={() => loadUserData(1)}>
        Load User Dashboard
      </button>
      
      {(userLoading || postsLoading) && <p>Loading...</p>}
      {user && <div>{user.name}</div>}
      {posts && <div>{posts.length} posts</div>}
    </div>
  );
}

How It Works

Here's the implementation:

import { useState, useEffect, useCallback, useRef } from 'react'

export interface UseAsyncOptions<T> {
  immediate?: boolean
  onSuccess?: (data: T) => void
  onError?: (error: Error) => void
}

export interface UseAsyncResult<T> {
  data: T | null
  error: Error | null
  loading: boolean
  execute: (...args: any[]) => Promise<T | null>
  reset: () => void
}

export function useAsync<T = any>(
  asyncFunction: (...args: any[]) => Promise<T>,
  options: UseAsyncOptions<T> = {}
): UseAsyncResult<T> {
  const [data, setData] = useState<T | null>(null)
  const [error, setError] = useState<Error | null>(null)
  const [loading, setLoading] = useState<boolean>(false)

  const { immediate = false, onSuccess, onError } = options
  const mountedRef = useRef(true)
  const asyncFunctionRef = useRef(asyncFunction)

  useEffect(() => {
    asyncFunctionRef.current = asyncFunction
  }, [asyncFunction])

  const execute = useCallback(
    async (...args: any[]): Promise<T | null> => {
      setLoading(true)
      setError(null)

      try {
        const result = await asyncFunctionRef.current(...args)
        
        if (mountedRef.current) {
          setData(result)
          setLoading(false)
          onSuccess?.(result)
        }
        
        return result
      } catch (err) {
        const error = err instanceof Error ? err : new Error('An error occurred')
        
        if (mountedRef.current) {
          setError(error)
          setLoading(false)
          onError?.(error)
        }
        
        return null
      }
    },
    [onSuccess, onError]
  )

  const reset = useCallback(() => {
    setData(null)
    setError(null)
    setLoading(false)
  }, [])

  useEffect(() => {
    if (immediate) {
      execute()
    }
  }, [immediate, execute])

  useEffect(() => {
    return () => {
      mountedRef.current = false
    }
  }, [])

  return { data, error, loading, execute, reset }
}

API Reference

Parameters

asyncFunction: (...args: any[]) => Promise<T>

The async function to execute

options?: UseAsyncOptions<T>

Optional configuration object

  • immediate: Execute function on mount (default: false)
  • onSuccess: Callback when execution succeeds
  • onError: Callback when execution fails

Return Value

data: T | null

The result of the async function

error: Error | null

Error object if execution failed

loading: boolean

True while the async function is executing

execute: (...args: any[]) => Promise<T | null>

Function to manually trigger execution

reset: () => void

Function to reset all state (data, error, loading)

💡 Use Cases

  • API calls and data fetching
  • Form submissions
  • File uploads
  • Database operations
  • Complex calculations
  • Any async operation that needs loading/error states