Implement a Reusable Toast Notification Component in React
Building interactive and user-friendly interfaces often involves providing feedback to the user for various actions. Toast notifications are a common and effective way to display brief, non-intrusive messages, such as success confirmations, error alerts, or informational updates. This challenge asks you to create a robust and reusable toast notification system in React.
Problem Description
Your task is to develop a React component that can display temporary, dismissible toast messages. The system should be flexible enough to handle different message types (e.g., success, error, info, warning) and allow for customization of display duration and dismissal behavior.
Key Requirements:
ToastContainerComponent: Create a component responsible for rendering all active toast messages. This component should be a single instance in your application, likely rendered at the root level.useToastHook: Develop a custom hook that allows any component in your application to trigger a toast notification. This hook should abstract away the logic of adding, displaying, and removing toasts.ToastComponent: Create an individualToastcomponent that represents a single notification. It should display the message and have a mechanism for dismissal.- Message Types: Support at least four distinct message types:
success,error,info, andwarning. These types should influence the visual styling of the toast. - Automatic Dismissal: Toasts should automatically disappear after a specified duration.
- Manual Dismissal: Users should be able to dismiss a toast manually (e.g., by clicking a close button).
- Dynamic Rendering: The
ToastContainershould dynamically add and removeToastcomponents as notifications are triggered and expire. - Styling: Provide basic styling for different toast types (e.g., green for success, red for error). The styling should be easily customizable.
- Reusability: The
useToasthook andToastContainershould be easily integrated into any React application.
Expected Behavior:
When a user triggers a toast (e.g., after a successful form submission), a toast message should appear on the screen (e.g., at the top-right corner). The toast should display the message and an optional icon or close button. After a set duration, the toast should fade out and disappear. If the user clicks the close button, the toast should disappear immediately. Multiple toasts can be visible simultaneously, stacked one above the other.
Edge Cases:
- What happens if many toasts are triggered quickly? (Consider a queue or limit).
- How should long messages be handled?
- What if the toast needs to be displayed for a very short or very long time?
Examples
Example 1: Triggering a Success Toast
// In a component where a form submission is successful
import { useToast } from './useToast'; // Assuming your hook is in this path
function MyForm() {
const { addToast } = useToast();
const handleSubmit = async () => {
// ... form submission logic ...
if (submissionSuccessful) {
addToast('Your data has been saved successfully!', { type: 'success', duration: 3000 });
}
};
return (
<button onClick={handleSubmit}>Submit</button>
);
}
// In your App.tsx or root component:
import { ToastContainer } from './ToastContainer'; // Assuming your container is in this path
function App() {
return (
<div>
<MyForm />
<ToastContainer />
</div>
);
}
Output: A toast with the message "Your data has been saved successfully!" appears at the top-right, styled as a success message, and disappears after 3 seconds.
Example 2: Triggering an Error Toast with Manual Dismissal
// In a component where an API call fails
import { useToast } from './useToast';
function DataFetcher() {
const { addToast } = useToast();
const fetchData = async () => {
try {
// ... API call ...
} catch (error) {
addToast('Failed to fetch data. Please try again later.', { type: 'error', dismissible: true });
}
};
return (
<button onClick={fetchData}>Fetch Data</button>
);
}
Output: An error toast appears with a close button. It will remain visible until manually dismissed or if a duration is also specified.
Example 3: Multiple Toasts and Different Types
If addToast is called multiple times in rapid succession:
addToast('Operation completed.', { type: 'info', duration: 5000 });addToast('Warning: Disk space is low.', { type: 'warning', duration: 7000 });addToast('Critical error encountered!', { type: 'error', duration: 10000 });
Output: Three toasts are displayed, stacked vertically. The info toast will disappear first, followed by the warning, and then the error toast. Each toast will have its respective styling.
Constraints
- The solution must be implemented in TypeScript.
- The
ToastContainershould ideally be a singleton managed by a context provider or similar mechanism to ensure only one instance is active. - The
useToasthook should return an object with at least anaddToastfunction. - The
addToastfunction should accept a message string and an optional configuration object (e.g.,{ type: 'success', duration?: number, dismissible?: boolean }). - Default
durationfor toasts should be 3000ms. - Toasts should be positioned consistently (e.g., top-right, bottom-center).
- Performance: The system should efficiently render and remove toasts without impacting application performance.
Notes
- Consider using React Context API to manage the state of toasts and provide the
addToastfunction through theuseToasthook. - For styling, you can use CSS modules, styled-components, or plain CSS. The important part is demonstrating how different types and states affect the appearance.
- Think about accessibility when designing the toast. Ensure screen readers can announce toast messages appropriately.
- For managing multiple toasts, you'll need an array to hold the active toast states. Each toast object in this array could have a unique ID, message, type, and expiration timestamp.
- You'll need a mechanism (like
setTimeout) to handle automatic dismissal. Ensure you clear these timeouts when a toast is manually dismissed or when the component unmounts to prevent memory leaks.