Skip to main content
Promise toasts provide a seamless way to show loading states that automatically transition to success or error based on your async operation’s result. Perfect for API calls, file uploads, and any asynchronous task.

Basic usage

The toast.promise() method takes a promise and message configuration for each state:
import { toast } from 'react-native-bread';

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

toast.promise(fetchUser('123'), {
  loading: { title: 'Loading user...' },
  success: { title: 'User loaded!' },
  error: (err) => ({ title: 'Failed to load user', description: err.message }),
});
1

Loading state appears

A loading toast is shown immediately with a spinner icon
2

Promise settles

The toast automatically updates when your promise resolves or rejects
3

Final state displays

Success (green checkmark) or error (red X) replaces the loading toast

Message configuration

Each state accepts either a string or an object with title, description, and duration:
toast.promise(apiCall(), {
  loading: 'Saving...',
  success: 'Saved!',
  error: 'Failed to save',
});

Error handling

The error message can be a static value or a function that receives the error:
toast.promise(deleteItem(id), {
  loading: 'Deleting...',
  success: 'Item deleted',
  // Function receives the error and can customize the message
  error: (err) => {
    if (err.message.includes('permission')) {
      return {
        title: 'Permission denied',
        description: 'You don\'t have access to delete this item',
      };
    }
    return {
      title: 'Failed to delete',
      description: err.message,
    };
  },
});
The error callback receives an Error object. If your promise rejects with a non-Error value, it will be wrapped in an Error automatically.

Return value

The toast.promise() method returns a promise that resolves with a result object:
const result = await toast.promise(fetchData(), {
  loading: 'Loading...',
  success: 'Done!',
  error: 'Failed',
});

if (result.success) {
  console.log('Data:', result.data);
} else {
  console.error('Error:', result.error);
}

Type signature

interface PromiseResult<T> {
  data?: T;
  error?: Error;
  success: boolean;
}
When success is true, data contains the promise’s resolved value. When false, error contains the error that was thrown.

Real-world examples

Form submission

const handleSubmit = async (formData: FormData) => {
  const result = await toast.promise(
    submitForm(formData),
    {
      loading: {
        title: 'Submitting form...',
        description: 'Please wait',
      },
      success: {
        title: 'Form submitted!',
        description: 'Your response has been recorded',
      },
      error: (err) => ({
        title: 'Submission failed',
        description: err.message,
      }),
    }
  );

  if (result.success) {
    navigation.navigate('Success');
  }
};

API request with network error handling

const loadUserProfile = async () => {
  const result = await toast.promise(
    api.get('/user/profile'),
    {
      loading: 'Loading profile...',
      success: 'Profile loaded',
      error: (err) => {
        // Customize based on error type
        if (err.message.includes('network')) {
          return {
            title: 'No internet connection',
            description: 'Please check your connection and try again',
          };
        }
        if (err.message.includes('401')) {
          return {
            title: 'Session expired',
            description: 'Please sign in again',
          };
        }
        return {
          title: 'Failed to load profile',
          description: 'Something went wrong',
        };
      },
    }
  );

  if (result.success) {
    setProfile(result.data);
  } else if (result.error?.message.includes('401')) {
    navigation.navigate('Login');
  }
};

File upload with progress

const uploadPhoto = async (uri: string) => {
  const result = await toast.promise(
    uploadToS3(uri),
    {
      loading: {
        title: 'Uploading photo...',
        description: 'Do not close the app',
        duration: 120000, // 2 minutes for large files
      },
      success: {
        title: 'Photo uploaded!',
        description: 'Your photo is now in your gallery',
        duration: 3000,
      },
      error: (err) => ({
        title: 'Upload failed',
        description: err.message.includes('size') 
          ? 'Photo is too large. Maximum size is 10MB'
          : 'Please try again',
        duration: 5000,
      }),
    }
  );

  if (result.success) {
    refreshGallery();
  }
};

Multiple sequential operations

const processOrder = async (orderId: string) => {
  // Step 1: Validate
  const validationResult = await toast.promise(
    validateOrder(orderId),
    {
      loading: 'Validating order...',
      success: 'Order validated',
      error: 'Validation failed',
    }
  );

  if (!validationResult.success) return;

  // Step 2: Process payment
  const paymentResult = await toast.promise(
    processPayment(orderId),
    {
      loading: 'Processing payment...',
      success: 'Payment successful',
      error: (err) => ({
        title: 'Payment failed',
        description: err.message,
      }),
    }
  );

  if (!paymentResult.success) return;

  // Step 3: Fulfill
  await toast.promise(
    fulfillOrder(orderId),
    {
      loading: 'Fulfilling order...',
      success: {
        title: 'Order complete!',
        description: 'You will receive a confirmation email',
      },
      error: 'Fulfillment failed',
    }
  );
};

Duration behavior

Loading toasts have a very long default duration (1 hour) to prevent them from auto-dismissing before the promise settles. You can override this if needed.
toast.promise(quickOperation(), {
  loading: {
    title: 'Loading...',
    duration: 5000, // Auto-dismiss loading state after 5s if promise hasn't settled
  },
  success: {
    title: 'Done!',
    duration: 3000, // Default is 4000ms
  },
  error: 'Failed', // Uses default 4000ms duration
});

TypeScript support

The promise result is fully typed based on your promise’s return type:
interface User {
  id: string;
  name: string;
}

const result = await toast.promise<User>(
  fetchUser('123'),
  {
    loading: 'Loading user...',
    success: 'User loaded',
    error: 'Failed',
  }
);

if (result.success) {
  // TypeScript knows result.data is User
  console.log(result.data.name);
}

API reference

toast.promise()

function promise<T>(
  promise: Promise<T>,
  messages: PromiseMessages
): Promise<PromiseResult<T>>

Parameters

  • promise: The promise to track
  • messages: Configuration object with three states:
    • loading: Message shown while promise is pending
    • success: Message shown when promise resolves
    • error: Message shown when promise rejects (can be a function)

Message types

type MessageInput = string | {
  title: string;
  description?: string;
  duration?: number;
}

type ErrorMessageInput = MessageInput | ((error: Error) => MessageInput)

interface PromiseMessages {
  loading: MessageInput;
  success: MessageInput;
  error: ErrorMessageInput;
}

Return type

interface PromiseResult<T> {
  data?: T;        // Available when success is true
  error?: Error;   // Available when success is false
  success: boolean; // true if resolved, false if rejected
}

Build docs developers (and LLMs) love