14 KiB
Notifications Store
The notifications store provides centralized state management for application-wide notifications. It handles the creation, management, and lifecycle of toast-style notifications with support for multiple types, positions, and interactive actions.
Overview
- Location:
src/stores/notifications.js - Type: Pinia Store
- Purpose: Global notification state management
- Integration: Used by NotificationDisplay component
Installation & Setup
// Import in your component
import { useNotificationStore } from "@/stores/notifications";
// Use in component
const notificationStore = useNotificationStore();
State Structure
Core State Properties
state: {
notifications: [], // Array of active notifications
defaultDuration: 4000, // Default auto-dismiss time (ms)
maxNotifications: 5, // Maximum concurrent notifications
position: 'top-right', // Default position
nextId: 1 // Auto-incrementing ID counter
}
Notification Object Structure
{
id: 1, // Unique identifier
type: 'success', // 'success' | 'error' | 'warning' | 'info'
title: 'Operation Complete', // Notification title
message: 'Data saved successfully', // Main message
duration: 4000, // Auto-dismiss time (0 = no auto-dismiss)
persistent: false, // If true, won't auto-dismiss
actions: [], // Array of action buttons
data: null, // Additional data for handlers
timestamp: '2025-11-12T10:30:00Z', // Creation timestamp
dismissed: false, // Whether notification is dismissed
seen: false // Whether user has interacted with it
}
Getters
getNotificationsByType(type)
Get all notifications of a specific type.
const errorNotifications = notificationStore.getNotificationsByType("error");
activeNotifications
Get all non-dismissed notifications.
const active = notificationStore.activeNotifications;
activeCount
Get count of active notifications.
const count = notificationStore.activeCount;
hasErrorNotifications
Check if there are any active error notifications.
const hasErrors = notificationStore.hasErrorNotifications;
hasSuccessNotifications
Check if there are any active success notifications.
const hasSuccess = notificationStore.hasSuccessNotifications;
Actions
Core Notification Methods
addNotification(notification)
Add a new notification with full configuration options.
const notificationId = notificationStore.addNotification({
type: "warning",
title: "Confirm Action",
message: "This will permanently delete the item.",
persistent: true,
actions: [
{
label: "Delete",
variant: "danger",
handler: () => performDelete(),
},
{
label: "Cancel",
variant: "secondary",
},
],
data: { itemId: 123 },
});
Convenience Methods
// Quick success notification
notificationStore.addSuccess("Operation completed!");
notificationStore.addSuccess("Custom message", "Custom Title", {
duration: 6000,
});
// Quick error notification
notificationStore.addError("Something went wrong!");
notificationStore.addError("Custom error", "Error Title", { persistent: true });
// Quick warning notification
notificationStore.addWarning("Please confirm this action");
// Quick info notification
notificationStore.addInfo("New feature available");
Notification Management
dismissNotification(id)
Mark a notification as dismissed (hides it but keeps in history).
notificationStore.dismissNotification(notificationId);
removeNotification(id)
Completely remove a notification from the store.
notificationStore.removeNotification(notificationId);
markAsSeen(id)
Mark a notification as seen (user has interacted with it).
notificationStore.markAsSeen(notificationId);
updateNotification(id, updates)
Update an existing notification's properties.
notificationStore.updateNotification(notificationId, {
type: "success",
message: "Updated message",
persistent: false,
});
Bulk Operations
clearType(type)
Remove all notifications of a specific type.
notificationStore.clearType("error"); // Remove all error notifications
clearAll()
Remove all notifications.
notificationStore.clearAll();
clearDismissed()
Remove all dismissed notifications from history.
notificationStore.clearDismissed();
Loading Notifications
showLoadingNotification(message, title)
Show a persistent loading notification that can be updated later.
const loadingId = notificationStore.showLoadingNotification(
"Uploading file...",
"Please Wait",
);
updateToSuccess(id, message, title)
Update a loading notification to success and auto-dismiss.
notificationStore.updateToSuccess(
loadingId,
"File uploaded successfully!",
"Upload Complete",
);
updateToError(id, message, title)
Update a loading notification to error and auto-dismiss.
notificationStore.updateToError(
loadingId,
"Upload failed. Please try again.",
"Upload Failed",
);
API Integration Helpers
showApiSuccess(operation, customMessage)
Show standardized success notifications for API operations.
// Uses default messages based on operation
notificationStore.showApiSuccess("create"); // "Item created successfully"
notificationStore.showApiSuccess("update"); // "Item updated successfully"
notificationStore.showApiSuccess("delete"); // "Item deleted successfully"
notificationStore.showApiSuccess("fetch"); // "Data loaded successfully"
// With custom message
notificationStore.showApiSuccess("create", "New client added successfully!");
showApiError(operation, error, customMessage)
Show standardized error notifications for API operations.
// Automatically extracts error message from different error formats
notificationStore.showApiError("create", apiError);
notificationStore.showApiError("update", "Network timeout occurred");
notificationStore.showApiError("delete", errorObject, "Custom error message");
Configuration Methods
setPosition(position)
Set the global notification position.
// Available positions:
notificationStore.setPosition("top-right"); // Default
notificationStore.setPosition("top-left");
notificationStore.setPosition("top-center");
notificationStore.setPosition("bottom-right");
notificationStore.setPosition("bottom-left");
notificationStore.setPosition("bottom-center");
setDefaultDuration(duration)
Set the default auto-dismiss duration (milliseconds).
notificationStore.setDefaultDuration(5000); // 5 seconds
setMaxNotifications(max)
Set maximum number of concurrent notifications.
notificationStore.setMaxNotifications(3);
Advanced Workflow Helper
withNotifications(operation, asyncFunction, options)
Wrap an async operation with automatic loading/success/error notifications.
const result = await notificationStore.withNotifications(
"save",
async () => {
return await saveDataToApi();
},
{
loadingMessage: "Saving changes...",
successMessage: "Changes saved successfully!",
errorMessage: null, // Use default error handling
showLoading: true,
},
);
Action Button Configuration
Action Object Structure
{
label: 'Button Text', // Required: Button label
variant: 'primary', // Optional: 'primary' | 'danger' | 'secondary'
icon: 'mdi mdi-check', // Optional: Icon class
handler: (notification) => {}, // Optional: Click handler function
dismissAfter: true // Optional: Auto-dismiss after click (default: true)
}
Action Examples
notificationStore.addNotification({
type: "warning",
title: "Unsaved Changes",
message: "You have unsaved changes. What would you like to do?",
persistent: true,
actions: [
{
label: "Save",
variant: "primary",
icon: "mdi mdi-content-save",
handler: (notification) => {
saveChanges();
// Access notification data if needed
console.log("Saving from notification:", notification.data);
},
},
{
label: "Discard",
variant: "danger",
icon: "mdi mdi-delete",
handler: () => {
discardChanges();
},
},
{
label: "Keep Editing",
variant: "secondary",
dismissAfter: false, // Don't dismiss, let user continue editing
},
],
});
Usage Patterns
Basic Notifications
const notificationStore = useNotificationStore();
// Simple success
notificationStore.addSuccess("Data saved successfully!");
// Simple error
notificationStore.addError("Failed to connect to server");
// Custom duration
notificationStore.addWarning("Session expires in 5 minutes", "Warning", {
duration: 8000,
});
API Operation Notifications
// Method 1: Manual handling
try {
await apiCall();
notificationStore.addSuccess("Operation completed successfully!");
} catch (error) {
notificationStore.addError(error.message, "Operation Failed");
}
// Method 2: Using API helpers
try {
await apiCall();
notificationStore.showApiSuccess("create");
} catch (error) {
notificationStore.showApiError("create", error);
}
// Method 3: Using workflow helper
await notificationStore.withNotifications("create", async () => {
return await apiCall();
});
Loading States
// Show loading notification
const loadingId = notificationStore.showLoadingNotification("Processing...");
try {
const result = await longRunningOperation();
notificationStore.updateToSuccess(loadingId, "Operation completed!");
} catch (error) {
notificationStore.updateToError(loadingId, "Operation failed");
}
Interactive Notifications
// Confirmation dialog
notificationStore.addNotification({
type: "warning",
title: "Delete Confirmation",
message: `Are you sure you want to delete "${itemName}"?`,
persistent: true,
actions: [
{
label: "Yes, Delete",
variant: "danger",
handler: async () => {
const deleteId =
notificationStore.showLoadingNotification("Deleting...");
try {
await deleteItem(itemId);
notificationStore.updateToSuccess(
deleteId,
"Item deleted successfully",
);
refreshData();
} catch (error) {
notificationStore.updateToError(deleteId, "Failed to delete item");
}
},
},
{
label: "Cancel",
variant: "secondary",
},
],
});
// Multi-step workflow
notificationStore.addNotification({
type: "info",
title: "Export Complete",
message: "Your data has been exported successfully.",
actions: [
{
label: "Download",
variant: "primary",
icon: "mdi mdi-download",
handler: () => downloadFile(),
},
{
label: "Email",
variant: "secondary",
icon: "mdi mdi-email",
handler: () => emailFile(),
},
{
label: "View",
variant: "secondary",
icon: "mdi mdi-eye",
handler: () => viewFile(),
},
],
});
Integration with Vue Components
In Composition API
import { useNotificationStore } from "@/stores/notifications";
export default {
setup() {
const notificationStore = useNotificationStore();
const handleSubmit = async () => {
try {
await submitForm();
notificationStore.addSuccess("Form submitted successfully!");
} catch (error) {
notificationStore.addError("Failed to submit form", "Submission Error");
}
};
return {
handleSubmit,
};
},
};
In Options API
import { mapActions, mapGetters } from "pinia";
import { useNotificationStore } from "@/stores/notifications";
export default {
computed: {
...mapGetters(useNotificationStore, [
"activeCount",
"hasErrorNotifications",
]),
},
methods: {
...mapActions(useNotificationStore, ["addSuccess", "addError", "clearAll"]),
async saveData() {
try {
await this.apiCall();
this.addSuccess("Data saved!");
} catch (error) {
this.addError(error.message);
}
},
},
};
Best Practices
Do's
- ✅ Use appropriate notification types for different scenarios
- ✅ Keep messages concise and actionable
- ✅ Use loading notifications for long-running operations
- ✅ Provide clear action buttons for next steps
- ✅ Set appropriate durations (longer for errors, shorter for success)
- ✅ Use persistent notifications for critical actions requiring user input
Don'ts
- ❌ Don't overwhelm users with too many notifications
- ❌ Don't use persistent notifications for simple confirmations
- ❌ Don't make notification messages too long
- ❌ Don't forget to handle loading state cleanup
- ❌ Don't use success notifications for every small action
Performance Considerations
- The store automatically limits concurrent notifications
- Dismissed notifications are kept in history for debugging
- Use
clearDismissed()periodically to prevent memory leaks - Action handlers should be lightweight to avoid blocking the UI
Testing
Unit Testing
import { setActivePinia, createPinia } from "pinia";
import { useNotificationStore } from "@/stores/notifications";
describe("Notifications Store", () => {
beforeEach(() => {
setActivePinia(createPinia());
});
it("adds notifications correctly", () => {
const store = useNotificationStore();
const id = store.addSuccess("Test message");
expect(store.activeNotifications).toHaveLength(1);
expect(store.activeNotifications[0].message).toBe("Test message");
expect(store.activeNotifications[0].type).toBe("success");
});
it("dismisses notifications", () => {
const store = useNotificationStore();
const id = store.addInfo("Test");
store.dismissNotification(id);
expect(store.activeNotifications).toHaveLength(0);
});
});