My App

Event handlers

Lifecycle hooks for custom logic during import

Event handlers let you hook into the import lifecycle to add custom validation, transformation, or handle submission.

onBeforeRowsChange

Fired after validation and transformation, and whenever a cell value changes.

Type signature:

onBeforeRowsChange?: (
  changedTableRows: ITableRows,
  range: ITableRowRange,
  fullTableRows: ITableRowsInternal
) => Promise<{ changedRows: ITableRows }>

Parameters:

ParameterTypeDescription
changedTableRowsITableRowsRows that changed (on initial load, all rows)
rangeITableRowRangeRange of changed rows { start: number, end: number }
fullTableRowsITableRowsInternalComplete table data (internal format)

Returns: Promise with { changedRows: ITableRows }

Use built-in features first

Before using this event, check if your use case can be solved with rules validators or data sources.

These built-in solutions are significantly faster because they run in web workers with parallel processing and don't block the main thread.

Only use onBeforeRowsChange when:

  • Your logic is truly impossible to express with rules or data sources
  • You need to perform complex async operations

Example: Remote API validation

This is a valid use case since remote validation isn't supported natively (yet).

eventHandlers: {
  onBeforeRowsChange: async (changedRows) => {
    for (const row of changedRows) {
      const email = row.email.currentValue as string;

      // Check if email exists in your system via API
      if (email) {
        const response = await fetch(`/api/check-email?email=${email}`);
        const { exists } = await response.json();

        if (exists) {
          row.email.newError = {
            type: 'custom',
            severity: 'e',
            message: 'This email is already registered'
          };
        }
      }
    }

    return { changedRows };
  }
}

Performance warning

This event runs on the main thread and can significantly slow down the import for large datasets. Each cell change triggers this function, so:

  • Avoid heavy computations
  • Minimize API calls (consider debouncing or batching)
  • Keep logic as simple as possible

For most validation and transformation needs, use the built-in validators, transformers, and data sources instead.


onSubmit

Fired when the user clicks submit. Receive the final validated data and send it to your backend.

Type signature:

onSubmit?: (
  tableRows: ITableRowsInternal,
  meta: IMeta,
  cellErrors: ICellErrorWithMeta[],
  submitMeta: CreateImportResponse | null
) => Promise<void>

Parameters:

ParameterTypeDescription
tableRowsITableRowsInternalFinal validated table data
metaIMetaImport metadata (file info, row count, error count, etc.)
cellErrorsICellErrorWithMeta[]Unresolved errors at submission time
submitMetaCreateImportResponse | nullIvandt server response (if using hosted mode)

Example:

eventHandlers: {
  onSubmit: async (tableRows, meta, cellErrors, submitMeta) => {
    // Send data to your backend
    const response = await fetch('/api/import', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        data: tableRows,
        fileName: meta.file.name,
        rowCount: meta.rowCount,
        errorCount: cellErrors.length
      })
    });

    if (!response.ok) {
      throw new Error('Import failed');
    }

    console.log('Import successful!');
  }
}

onExport

Fired when the user clicks on the export button which exports the current table rows as an Excel file. Note that all the tableRows will be exported, regardless of whether they have errors or not

Type signature:

onExport?: (
  tableRows: ITableRowsInternal,
  meta: IMeta,
  cellErrors: ICellErrorWithMeta[],
) => Promise<void>

Parameters:

ParameterTypeDescription
tableRowsITableRowsInternalAll table rows at the time of export
metaIMetaImport metadata (file info, row count, error count, etc.)
cellErrorsICellErrorWithMeta[]Unresolved errors at export time

Example:

eventHandlers: {
  onExport: async (tableRows, meta, cellErrors) => {

    console.log('User exported data',tableRows);
  }
}

onSubmitError

Fired if an unexpected error occurs during submission.

Type signature:

onSubmitError?: (error: any) => void

Parameters:

ParameterTypeDescription
erroranyThe error that occurred

Example:

eventHandlers: {
  onSubmitError: (error) => {
    console.error('Submission failed:', error);
    alert('Failed to submit data. Please try again.');
  }
}

onFileUpload

Fired immediately after the user selects a file. Use this to upload the file to your server for processing or storage.

Type signature:

onFileUpload?: (file: File, fileMeta: IFileInfo) => void

Parameters:

ParameterTypeDescription
fileFileThe uploaded file object
fileMetaIFileInfoFile metadata (name, size, type, etc.)

Example:

eventHandlers: {
  onFileUpload: async (file, fileMeta) => {
    const formData = new FormData();
    formData.append('file', file);

    await fetch('/api/upload', {
      method: 'POST',
      body: formData
    });

    console.log(`Uploaded ${fileMeta.name} (${fileMeta.size} bytes)`);
  }
}

onCancelImporter

Will be fired if the cancel button of the upload step has been enabled, and the user clicks on the cancel button that's shown in the stepper

Type signature:

onCancelImporter?: () => void

Example:

eventHandlers: {
  onCancelImporter: async () => {
    console.log(`Importer was cancelled, maybe navigate the user to another page?`);
  }
}

onClose

Will be fired after the data submission has finished successfully and the user has seen the success message and clicks on the close button It's useful to use this event to redirect the user to another page or something like that. Alternatively, you could redirect the user inside onSubmit, in which case, the user won't get the see the importer's success message at all

Type signature:

onClose?: () => void

Example:

eventHandlers: {
  onClose: async () => {
    console.log(`Importer was closed after it was successfully submitted, maybe redirect to another page`);
  }
}

onReady

Will be fired when the importer is loaded in DOM, and it's ready

Type signature:

onReady?: (rootElement: HTMLElement) => void

Parameters:

ParameterTypeDescription
rootElementHTMLElementThe root HTML element that hosts the importer. This is the first child of the shadow root element

Example:

eventHandlers: {
  onReady: async (rootElement:HTMLElement) => {
    console.log(`Importer is ready rootElement`,rootElement);
  }
}

On this page