CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-uppy--core

Core module for the extensible JavaScript file upload widget with support for drag&drop, resumable uploads, previews, restrictions, file processing/encoding, remote providers like Instagram, Dropbox, Google Drive, S3 and more

Pending
Overview
Eval results
Files

event-management.mddocs/

Event Management

Uppy provides a comprehensive event system for handling all aspects of the file upload lifecycle. Events can be used for monitoring progress, implementing custom logic, and integrating with external systems.

EventManager Class

Utility class for managing Uppy event subscriptions with automatic cleanup capabilities.

Constructor

constructor(uppy: Uppy<M, B>)

Event Subscription

on<K extends keyof UppyEventMap<M, B>>(
  event: K,
  fn: UppyEventMap<M, B>[K]
): Uppy<M, B>;

Subscribes to an event and tracks the subscription for later cleanup.

Cleanup

remove(): void;

Removes all event listeners registered through this EventManager instance.

File-Specific Event Helpers

onFilePause(fileID: UppyFile<M, B>['id'], cb: (isPaused: boolean) => void): void;
onFileRemove(fileID: UppyFile<M, B>['id'], cb: (isPaused: UppyFile<M, B>['id']) => void): void;
onPause(fileID: UppyFile<M, B>['id'], cb: (isPaused: boolean) => void): void;
onRetry(fileID: UppyFile<M, B>['id'], cb: () => void): void;
onRetryAll(fileID: UppyFile<M, B>['id'], cb: () => void): void;
onPauseAll(fileID: UppyFile<M, B>['id'], cb: () => void): void;
onCancelAll(fileID: UppyFile<M, B>['id'], eventHandler: UppyEventMap<M, B>['cancel-all']): void;
onResumeAll(fileID: UppyFile<M, B>['id'], cb: () => void): void;

These methods listen for global events but only trigger callbacks when they affect the specified file.

Core Uppy Events

File Events

interface UppyEventMap<M extends Meta, B extends Body> {
  'file-added': (file: UppyFile<M, B>) => void;
  'file-removed': (file: UppyFile<M, B>) => void;
  'files-added': (files: UppyFile<M, B>[]) => void;
}

file-added: Fired when a single file is added to Uppy file-removed: Fired when a file is removed from Uppy files-added: Fired when multiple files are added at once

Upload Control Events

interface UppyEventMap<M extends Meta, B extends Body> {
  'upload': (uploadID: string, files: UppyFile<M, B>[]) => void;
  'upload-start': (files: UppyFile<M, B>[]) => void;
  'cancel-all': () => void;
  'pause-all': () => void;
  'resume-all': () => void;
  'retry-all': (files: UppyFile<M, B>[]) => void;
}

upload: Fired when upload process begins upload-start: Alternative event for upload start cancel-all: Fired when all uploads are cancelled pause-all: Fired when all uploads are paused resume-all: Fired when all uploads are resumed retry-all: Fired when retrying all failed uploads

Upload Progress Events

interface UppyEventMap<M extends Meta, B extends Body> {
  'upload-progress': (
    file: UppyFile<M, B> | undefined,
    progress: FileProgressStarted
  ) => void;
  'progress': (progress: number) => void;
  'upload-success': (
    file: UppyFile<M, B> | undefined,
    response: NonNullable<UppyFile<M, B>['response']>
  ) => void;
  'upload-error': (
    file: UppyFile<M, B> | undefined,
    error: { name: string; message: string; details?: string },
    response?: Omit<NonNullable<UppyFile<M, B>['response']>, 'uploadURL'> | undefined
  ) => void;
}

upload-progress: Progress update for individual file upload progress: Overall upload progress (0-100) upload-success: File upload completed successfully upload-error: File upload failed

Upload State Events

interface UppyEventMap<M extends Meta, B extends Body> {
  'upload-pause': (file: UppyFile<M, B> | undefined, isPaused: boolean) => void;
  'upload-retry': (file: UppyFile<M, B>) => void;
  'upload-stalled': (
    error: { message: string; details?: string },
    files: UppyFile<M, B>[]
  ) => void;
}

upload-pause: File upload paused or resumed upload-retry: File upload is being retried upload-stalled: Upload appears to be stalled

Processing Events

interface UppyEventMap<M extends Meta, B extends Body> {
  'preprocess-progress': (
    file: UppyFile<M, B> | undefined,
    progress: NonNullable<FileProgressStarted['preprocess']>
  ) => void;
  'preprocess-complete': (
    file: UppyFile<M, B> | undefined,
    progress?: NonNullable<FileProgressStarted['preprocess']>
  ) => void;
  'postprocess-progress': (
    file: UppyFile<M, B> | undefined,
    progress: NonNullable<FileProgressStarted['postprocess']>
  ) => void;
  'postprocess-complete': (
    file: UppyFile<M, B> | undefined,
    progress?: NonNullable<FileProgressStarted['preprocess']>
  ) => void;
}

preprocess-progress: Progress update during file preprocessing preprocess-complete: File preprocessing completed postprocess-progress: Progress update during file postprocessing postprocess-complete: File postprocessing completed

Validation Events

interface UppyEventMap<M extends Meta, B extends Body> {
  'restriction-failed': (file: UppyFile<M, B> | undefined, error: Error) => void;
}

restriction-failed: File failed validation against restrictions

State Management Events

interface UppyEventMap<M extends Meta, B extends Body> {
  'state-update': (
    prevState: State<M, B>,
    nextState: State<M, B>,
    patch?: Partial<State<M, B>>
  ) => void;
}

state-update: Uppy state has changed

Information Events

interface UppyEventMap<M extends Meta, B extends Body> {
  'info-visible': () => void;
  'info-hidden': () => void;
}

info-visible: Info message is now visible to user info-hidden: Info message has been hidden

Network Events

interface UppyEventMap<M extends Meta, B extends Body> {
  'is-offline': () => void;
  'is-online': () => void;
}

is-offline: Internet connection lost is-online: Internet connection restored

Plugin Events

interface UppyEventMap<M extends Meta, B extends Body> {
  'plugin-added': (plugin: UnknownPlugin<any, any>) => void;
  'plugin-remove': (plugin: UnknownPlugin<any, any>) => void;
}

plugin-added: Plugin has been installed plugin-remove: Plugin has been removed

Restoration Events

interface UppyEventMap<M extends Meta, B extends Body> {
  restored: (pluginData: any) => void;
  'restore-confirmed': () => void;
  'restore-canceled': () => void;
}

restored: Upload state has been restored restore-confirmed: User confirmed state restoration restore-canceled: User canceled state restoration

Event Usage Examples

Basic Event Handling

import Uppy from '@uppy/core';

const uppy = new Uppy();

// File lifecycle events
uppy.on('file-added', (file) => {
  console.log('File added:', file.name, file.size);
});

uppy.on('file-removed', (file) => {
  console.log('File removed:', file.name);
});

// Upload progress tracking
uppy.on('upload-progress', (file, progress) => {
  if (file) {
    console.log(`${file.name}: ${progress.bytesUploaded}/${progress.bytesTotal}`);
  }
});

uppy.on('upload-success', (file, response) => {
  console.log('Upload successful:', file?.name, response.body);
});

uppy.on('upload-error', (file, error, response) => {
  console.error('Upload failed:', file?.name, error.message);
});

Progress Monitoring

let totalProgress = 0;

uppy.on('progress', (progress) => {
  totalProgress = progress;
  updateProgressBar(progress);
});

uppy.on('upload-start', (files) => {
  console.log(`Starting upload of ${files.length} files`);
  showProgressModal();
});

uppy.on('complete', (result) => {
  console.log('Upload complete:', result);
  hideProgressModal();
  
  if (result.failed.length > 0) {
    showErrorMessage(`${result.failed.length} files failed to upload`);
  } else {
    showSuccessMessage(`${result.successful.length} files uploaded successfully`);
  }
});

function updateProgressBar(progress) {
  const progressBar = document.querySelector('#progress-bar');
  progressBar.style.width = `${progress}%`;
  progressBar.textContent = `${Math.round(progress)}%`;
}

File Validation Events

uppy.on('restriction-failed', (file, error) => {
  console.log('File restriction failed:', file?.name, error.message);
  
  // Show custom error message to user
  if (error.message.includes('size')) {
    showNotification('File is too large', 'error');
  } else if (error.message.includes('type')) {
    showNotification('File type not allowed', 'error');
  }
});

// Pre-upload validation
uppy.on('upload', (uploadID, files) => {
  // Custom validation before upload starts
  const invalidFiles = files.filter(file => 
    !file.name.match(/^[a-zA-Z0-9_-]+\.[a-zA-Z0-9]+$/)
  );
  
  if (invalidFiles.length > 0) {
    uppy.info('Some files have invalid names', 'error');
    invalidFiles.forEach(file => uppy.removeFile(file.id));
  }
});

EventManager Usage

import Uppy, { EventManager } from '@uppy/core';

class FileUploadComponent {
  private uppy: Uppy;
  private eventManager: EventManager<any, any>;

  constructor() {
    this.uppy = new Uppy();
    this.eventManager = new EventManager(this.uppy);
    this.setupEvents();
  }

  setupEvents() {
    // All events registered through EventManager
    this.eventManager.on('file-added', this.handleFileAdded);
    this.eventManager.on('upload-success', this.handleUploadSuccess);
    this.eventManager.on('upload-error', this.handleUploadError);
    
    // File-specific event helpers
    this.eventManager.onFilePause('specific-file-id', (isPaused) => {
      console.log('File pause state changed:', isPaused);
    });
  }

  handleFileAdded = (file) => {
    // Update component state
    this.updateFileList();
  };

  handleUploadSuccess = (file, response) => {
    // Handle successful upload
    this.processUploadResult(file, response);
  };

  handleUploadError = (file, error) => {
    // Handle upload error
    this.showErrorForFile(file, error);
  };

  destroy() {
    // Clean up all event listeners at once
    this.eventManager.remove();
    this.uppy.destroy();
  }
}

State Monitoring

uppy.on('state-update', (prevState, nextState, patch) => {
  console.log('State updated:', patch);
  
  // React to specific state changes
  if (patch?.files) {
    const fileIds = Object.keys(patch.files);
    fileIds.forEach(fileId => {
      const fileUpdate = patch.files[fileId];
      if (fileUpdate.progress) {
        updateFileProgress(fileId, fileUpdate.progress);
      }
    });
  }
  
  if (patch?.totalProgress !== undefined) {
    updateOverallProgress(patch.totalProgress);
  }
});

Plugin Communication

// Plugin emitting custom events
class MyPlugin extends BasePlugin {
  doSomething() {
    // Emit custom event
    this.uppy.emit('my-plugin:action-completed', { data: 'result' });
  }
}

// Listening for plugin events
uppy.on('my-plugin:action-completed', (result) => {
  console.log('Plugin action completed:', result);
});

// Plugin lifecycle events
uppy.on('plugin-added', (plugin) => {
  console.log('Plugin added:', plugin.id, plugin.type);
  
  if (plugin.type === 'dashboard') {
    // Configure dashboard plugin
    plugin.setOptions({ theme: 'dark' });
  }
});

Network Status Handling

uppy.on('is-offline', () => {
  console.log('Connection lost');
  uppy.info('Connection lost. Uploads will resume when back online.', 'warning');
  pauseAllUploads();
});

uppy.on('is-online', () => {
  console.log('Connection restored');
  uppy.info('Connection restored. Resuming uploads.', 'success');
  resumeAllUploads();
});

function pauseAllUploads() {
  uppy.pauseAll();
  showOfflineIndicator();
}

function resumeAllUploads() {
  uppy.resumeAll();
  hideOfflineIndicator();
}

Event Best Practices

Event Cleanup

Always remove event listeners when components are destroyed to prevent memory leaks:

// Manual cleanup
const handler = (file) => console.log(file);
uppy.on('file-added', handler);
// Later...
uppy.off('file-added', handler);

// Or use EventManager for automatic cleanup
const eventManager = new EventManager(uppy);
eventManager.on('file-added', handler);
// Later...
eventManager.remove(); // Removes all managed listeners

Error Handling

Wrap event handlers in try-catch blocks to prevent errors from breaking the upload process:

uppy.on('upload-success', (file, response) => {
  try {
    processUploadResponse(file, response);
  } catch (error) {
    console.error('Error processing upload response:', error);
    uppy.info('Upload succeeded but processing failed', 'warning');
  }
});

Performance Considerations

  • Use debouncing for frequently fired events like progress and upload-progress
  • Avoid heavy computations in event handlers
  • Consider using once() for one-time event handling
// Debounced progress handler
let progressTimeout;
uppy.on('progress', (progress) => {
  clearTimeout(progressTimeout);
  progressTimeout = setTimeout(() => {
    updateProgressDisplay(progress);
  }, 100);
});

Install with Tessl CLI

npx tessl i tessl/npm-uppy--core

docs

event-management.md

file-validation.md

index.md

plugin-development.md

type-system.md

uppy-class.md

tile.json