15 KiB

Errors Store

The errors store provides comprehensive error handling and management for the entire application. It centralizes error tracking, automatic retry logic, and integration with the notification system to provide users with consistent error feedback.

Overview

  • Location: src/stores/errors.js
  • Type: Pinia Store
  • Purpose: Centralized error state management and handling
  • Integration: Works with notification store for user feedback

Installation & Setup

// Import in your component
import { useErrorStore } from "@/stores/errors";

// Use in component
const errorStore = useErrorStore();

State Structure

Core State Properties

state: {
  hasError: false,              // Global error flag
  lastError: null,              // Most recent global error
  apiErrors: new Map(),         // API-specific errors by key
  componentErrors: {            // Component-specific errors
    dataTable: null,
    form: null,
    clients: null,
    jobs: null,
    timesheets: null,
    warranties: null,
    routes: null
  },
  errorHistory: [],             // Historical error log
  maxHistorySize: 50,           // Maximum history entries
  autoNotifyErrors: true        // Auto-show notifications for errors
}

Error Object Structure

{
  message: 'Error description',       // Human-readable error message
  type: 'api_error',                 // Error classification
  timestamp: '2025-11-12T10:30:00Z', // When error occurred
  name: 'ValidationError',           // Error name (if available)
  stack: 'Error stack trace...',     // Stack trace (if available)
  status: 404,                       // HTTP status (for API errors)
  statusText: 'Not Found',           // HTTP status text
  data: {...}                        // Additional error data
}

Getters

hasAnyError

Check if there are any errors in the application.

const hasErrors = errorStore.hasAnyError;

getComponentError(componentName)

Get error for a specific component.

const formError = errorStore.getComponentError("form");

getApiError(apiKey)

Get error for a specific API operation.

const loginError = errorStore.getApiError("user-login");

getRecentErrors(limit)

Get recent errors from history.

const recentErrors = errorStore.getRecentErrors(10);

Actions

Global Error Management

setGlobalError(error, showNotification)

Set a global application error.

errorStore.setGlobalError(
  new Error("Critical system failure"),
  true, // Show notification
);

// With custom error object
errorStore.setGlobalError({
  message: "Database connection lost",
  type: "connection_error",
  recoverable: true,
});

clearGlobalError()

Clear the global error state.

errorStore.clearGlobalError();

Component-Specific Error Management

setComponentError(componentName, error, showNotification)

Set an error for a specific component.

// Set error for form component
errorStore.setComponentError("form", new Error("Validation failed"), true);

// Clear error (pass null)
errorStore.setComponentError("form", null);

clearComponentError(componentName)

Clear error for a specific component.

errorStore.clearComponentError("form");

API Error Management

setApiError(apiKey, error, showNotification)

Set an error for a specific API operation.

// Set API error
errorStore.setApiError("user-login", apiError, true);

// Clear API error (pass null)
errorStore.setApiError("user-login", null);

clearApiError(apiKey)

Clear error for a specific API operation.

errorStore.clearApiError("user-login");

Bulk Operations

clearAllErrors()

Clear all errors from the store.

errorStore.clearAllErrors();

Advanced Error Handling

handleApiCall(apiKey, apiFunction, options)

Handle an API call with automatic error management and retry logic.

const result = await errorStore.handleApiCall(
  "fetch-users",
  async () => {
    return await api.getUsers();
  },
  {
    showNotification: true,
    retryCount: 2,
    retryDelay: 1000,
    onSuccess: (result) => console.log("Success:", result),
    onError: (error) => console.log("Failed:", error),
  },
);

withErrorHandling(operationKey, asyncOperation, options)

Wrap an async operation with comprehensive error handling.

const result = await errorStore.withErrorHandling(
  "save-data",
  async () => {
    return await saveUserData();
  },
  {
    componentName: "userForm",
    showNotification: true,
    rethrow: false, // Don't re-throw errors
  },
);

Error Types

The store automatically categorizes errors into different types:

string_error

Simple string errors.

errorStore.setGlobalError("Something went wrong");

javascript_error

Standard JavaScript Error objects.

errorStore.setGlobalError(new Error("Validation failed"));

api_error

HTTP/API response errors with status codes.

// Automatically detected from axios-style error objects
{
  message: 'Not Found',
  status: 404,
  statusText: 'Not Found',
  type: 'api_error',
  data: {...}
}

network_error

Network connectivity errors.

{
  message: 'Network error - please check your connection',
  type: 'network_error'
}

unknown_error

Unrecognized error formats.

{
  message: 'An unknown error occurred',
  type: 'unknown_error',
  originalError: {...}
}

Configuration Methods

setAutoNotifyErrors(enabled)

Enable/disable automatic error notifications.

errorStore.setAutoNotifyErrors(false); // Disable auto-notifications

setMaxHistorySize(size)

Set maximum number of errors to keep in history.

errorStore.setMaxHistorySize(100);

Usage Patterns

Basic Error Handling

const errorStore = useErrorStore();

// Simple error setting
try {
  await riskyOperation();
} catch (error) {
  errorStore.setGlobalError(error);
}

// Component-specific error
try {
  await validateForm();
} catch (validationError) {
  errorStore.setComponentError("form", validationError);
}

API Error Handling with Retry

// Automatic retry logic
const userData = await errorStore.handleApiCall(
  "fetch-user-data",
  async () => {
    const response = await fetch("/api/users");
    if (!response.ok) throw new Error("Failed to fetch users");
    return response.json();
  },
  {
    retryCount: 3,
    retryDelay: 1000,
    showNotification: true,
  },
);

Comprehensive Operation Wrapping

// Wrap complex operations
const result = await errorStore.withErrorHandling(
  "complex-workflow",
  async () => {
    // Step 1: Validate data
    await validateInputData();

    // Step 2: Save to database
    const saveResult = await saveToDatabase();

    // Step 3: Send notification email
    await sendNotificationEmail();

    return saveResult;
  },
  {
    componentName: "workflow",
    showNotification: true,
    rethrow: false,
  },
);

if (result) {
  // Success - result contains the return value
  console.log("Workflow completed:", result);
} else {
  // Error occurred - check component error for details
  const workflowError = errorStore.getComponentError("workflow");
  console.log("Workflow failed:", workflowError?.message);
}

Integration with Vue Components

// In a Vue component
import { useErrorStore } from "@/stores/errors";
import { useNotificationStore } from "@/stores/notifications";

export default {
  setup() {
    const errorStore = useErrorStore();
    const notificationStore = useNotificationStore();

    const submitForm = async (formData) => {
      // Clear any previous errors
      errorStore.clearComponentError("form");

      try {
        await errorStore.withErrorHandling(
          "form-submit",
          async () => {
            const result = await api.submitForm(formData);
            notificationStore.addSuccess("Form submitted successfully!");
            return result;
          },
          {
            componentName: "form",
            showNotification: true,
          },
        );

        // Reset form on success
        resetForm();
      } catch (error) {
        // Error handling is automatic, but you can add custom logic here
        console.log("Form submission failed");
      }
    };

    return {
      submitForm,
      formError: computed(() => errorStore.getComponentError("form")),
    };
  },
};

Integration with Enhanced API

The errors store works seamlessly with the enhanced API wrapper:

import { ApiWithErrorHandling } from "@/api-enhanced";

// The enhanced API automatically uses the error store
try {
  const clients = await ApiWithErrorHandling.getPaginatedClientDetails(
    pagination,
    filters,
    [],
    {
      componentName: "clients",
      retryCount: 2,
      showErrorNotifications: true,
    },
  );
} catch (error) {
  // Error is automatically handled by the error store
  // Check component error for details
  const clientError = errorStore.getComponentError("clients");
}

Error Recovery Patterns

Graceful Degradation

const loadCriticalData = async () => {
  let primaryData = null;
  let fallbackData = null;

  // Try primary data source
  try {
    primaryData = await errorStore.withErrorHandling(
      "primary-data",
      () => api.getPrimaryData(),
      { showNotification: false, rethrow: true },
    );
  } catch (error) {
    console.log("Primary data failed, trying fallback...");

    // Try fallback data source
    try {
      fallbackData = await errorStore.withErrorHandling(
        "fallback-data",
        () => api.getFallbackData(),
        { showNotification: false, rethrow: true },
      );

      notificationStore.addWarning(
        "Using cached data due to connectivity issues",
      );
    } catch (fallbackError) {
      errorStore.setGlobalError("Unable to load data from any source");
      return null;
    }
  }

  return primaryData || fallbackData;
};

User-Driven Error Recovery

const handleApiError = async (operation) => {
  try {
    return await operation();
  } catch (error) {
    // Show error with recovery options
    notificationStore.addNotification({
      type: "error",
      title: "Operation Failed",
      message: "Would you like to try again or continue with cached data?",
      persistent: true,
      actions: [
        {
          label: "Retry",
          variant: "primary",
          handler: () => handleApiError(operation), // Recursive retry
        },
        {
          label: "Use Cached Data",
          variant: "secondary",
          handler: () => loadCachedData(),
        },
        {
          label: "Cancel",
          variant: "secondary",
        },
      ],
    });

    throw error; // Let caller handle as needed
  }
};

Debugging & Monitoring

Error History Access

// Get all error history for debugging
const allErrors = errorStore.errorHistory;

// Get recent errors with details
const recent = errorStore.getRecentErrors(5);
recent.forEach((error) => {
  console.log(`[${error.timestamp}] ${error.source}: ${error.message}`);
});

// Filter errors by type
const apiErrors = errorStore.errorHistory.filter((e) => e.type === "api_error");

Error Reporting

// Send error reports to monitoring service
const reportErrors = () => {
  const recentErrors = errorStore.getRecentErrors(10);

  recentErrors.forEach((error) => {
    if (error.type === "api_error" && error.status >= 500) {
      // Report server errors
      analyticsService.reportError({
        message: error.message,
        url: window.location.href,
        userAgent: navigator.userAgent,
        timestamp: error.timestamp,
        stack: error.stack,
      });
    }
  });
};

Testing

Unit Testing

import { setActivePinia, createPinia } from "pinia";
import { useErrorStore } from "@/stores/errors";

describe("Errors Store", () => {
  beforeEach(() => {
    setActivePinia(createPinia());
  });

  it("sets and clears global errors", () => {
    const store = useErrorStore();

    store.setGlobalError("Test error");
    expect(store.hasError).toBe(true);
    expect(store.lastError.message).toBe("Test error");

    store.clearGlobalError();
    expect(store.hasError).toBe(false);
  });

  it("handles component errors", () => {
    const store = useErrorStore();

    store.setComponentError("form", new Error("Validation failed"));
    expect(store.getComponentError("form").message).toBe("Validation failed");

    store.clearComponentError("form");
    expect(store.getComponentError("form")).toBeNull();
  });

  it("tracks error history", () => {
    const store = useErrorStore();

    store.setGlobalError("Error 1");
    store.setGlobalError("Error 2");

    expect(store.errorHistory).toHaveLength(2);
    expect(store.getRecentErrors(1)[0].message).toBe("Error 2");
  });
});

Integration Testing

// Test error handling with API calls
it("handles API errors correctly", async () => {
  const store = useErrorStore();
  const mockApi = jest.fn().mockRejectedValue(new Error("API Error"));

  const result = await store.withErrorHandling("test-api", mockApi, {
    componentName: "test",
    showNotification: false,
    rethrow: false,
  });

  expect(result).toBeNull();
  expect(store.getComponentError("test").message).toBe("API Error");
});

Best Practices

Do's

  • Use component-specific errors for UI validation
  • Use API-specific errors for network operations
  • Enable auto-notifications for user-facing errors
  • Use retry logic for transient failures
  • Clear errors when operations succeed
  • Keep error messages user-friendly and actionable

Don'ts

  • Don't set global errors for minor validation issues
  • Don't ignore error context (component/API source)
  • Don't let error history grow indefinitely
  • Don't show technical stack traces to end users
  • Don't retry operations that will consistently fail

Performance Considerations

  • Error history is automatically trimmed to prevent memory leaks
  • Use component-specific errors to isolate issues
  • Clear errors promptly when no longer relevant
  • Consider disabling auto-notifications for high-frequency operations

Common Patterns

Form Validation Errors

const validateAndSubmit = async (formData) => {
  errorStore.clearComponentError("form");

  try {
    await errorStore.withErrorHandling(
      "form-validation",
      async () => {
        validateFormData(formData);
        await submitForm(formData);
      },
      {
        componentName: "form",
        showNotification: true,
      },
    );
  } catch (error) {
    // Validation errors are now stored in component error
    // and automatically displayed to user
  }
};

Background Task Monitoring

const monitorBackgroundTask = async (taskId) => {
  await errorStore.handleApiCall(
    `task-${taskId}`,
    async () => {
      const status = await api.getTaskStatus(taskId);

      if (status.failed) {
        throw new Error(`Task failed: ${status.error}`);
      }

      return status;
    },
    {
      retryCount: 5,
      retryDelay: 2000,
      showNotification: status.failed, // Only notify on final failure
    },
  );
};