How the TCG Pocket Collection Tracker Import Feature Parses and Validates CSV Card Data

The collection import feature uses the xlsx library to parse CSV and Excel files, enforces non-negative numeric validation on card quantities, and leverages a Collected boolean flag to determine whether to create, update, or delete collection entries while surfacing errors through React Query mutations and real-time progress feedback.

The tcg-pocket-collection-tracker repository implements a robust data ingestion pipeline for trading card collections. Understanding how this collection import feature parse and validate CSV formatted card data reveals a multi-layered approach that combines browser-native file APIs, schema validation, and transactional state management to prevent data corruption.

File Reading and Initial Parsing

The import workflow begins in frontend/src/pages/import/components/ImportReader.tsx. When a user drops a CSV or Excel file onto the component, the system initiates a safe reading process using the browser's native FileReader API.

The component configures three critical event handlers to manage the file reading lifecycle:

const reader = new FileReader();

reader.onabort = () => {
  setIsLoading(false);
  setErrorMessage(t('fileWasAborted'));
};

reader.onerror = () => {
  setIsLoading(false);
  setErrorMessage(t('errorWithFile'));
};

Once the file loads successfully, the raw bytes pass to the XLSX.read method from the xlsx library. The parser extracts the first worksheet and converts it into a typed array of ImportExportRow objects defined in frontend/src/types/index.ts:

const workbook = XLSX.read(e.target?.result);
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
const rows = XLSX.utils.sheet_to_json<ImportExportRow>(worksheet);

Data Validation and Normalization

After parsing, the processFileRows function iterates through each row to validate and sanitize the incoming data. The primary validation concern involves ensuring that card quantities remain non-negative integers.

The system applies Math.max(0, Number(r.NumberOwned)) to coerce and clamp the value:

const newAmount = Math.max(0, Number(r.NumberOwned));

This defensive programming technique prevents negative inventory counts that could arise from malformed CSV cells or user error. The code also performs lookups against the existing collection state using ownedCards.get(r.InternalId) to detect mismatches between the imported data and the current database state.

Collection State Logic

The import feature implements sophisticated upsert and delete logic based on the Collected boolean flag present in each CSV row. This flag determines whether the operation should add a card to the collection or remove it.

When r.Collected evaluates to true, the system constructs a payload for the updateCardsMutation:

if (r.Collected) {
  cardArray.push({ 
    card_id: r.Id, 
    internal_id: r.InternalId, 
    amount_owned: newAmount 
  });
}

Conversely, when the flag is false but the card exists in the user's current collection, the system queues it for deletion:

else if (ownedCards.get(r.InternalId)?.collection.includes(r.Id)) {
  cardIdsToDelete.push(r.Id);
}

This bidirectional synchronization ensures that importing a CSV with Collected: false effectively removes cards from the digital collection, maintaining parity between the external spreadsheet and the application state.

Error Handling and User Feedback

The import pipeline implements comprehensive error handling at multiple architectural layers. A global try...catch block wraps the entire parsing workflow within the reader.onload callback:

try {
  // ... parsing and validation logic
} catch (error) {
  console.error(error);
  setErrorMessage(t('errorProcessingExcel') + error);
} finally {
  setIsLoading(false);
}

This ensures that malformed CSV structures, missing columns, or unexpected data types surface as user-friendly error messages rather than crashing the application. The finally block guarantees that the loading state resets, preventing UI lockups.

Real-time progress feedback occurs through React state updates within the row processing loop:

setProgressMessage(`Processing ${r.CardName}...`);
setNumberProcessed((prev) => prev + 1);

These updates provide visual confirmation during large imports, allowing users to track which card is currently being processed and how many remain.

Summary

  • The import feature resides in frontend/src/pages/import/components/ImportReader.tsx and utilizes the xlsx library to parse both CSV and Excel formats.
  • Data validation enforces non-negative quantities using Math.max(0, Number(r.NumberOwned)) to sanitize user input.
  • The Collected boolean flag drives the business logic, determining whether to execute an upsert mutation or queue the card for deletion.
  • Error handling spans FileReader aborts, parsing exceptions, and React Query mutation failures, with user feedback provided via localized toast messages and real-time progress indicators.
  • The ImportExportRow type definition in frontend/src/types/index.ts provides the TypeScript contract that ensures column name consistency between the CSV header and the application logic.

Frequently Asked Questions

What happens if the CSV contains negative numbers in the NumberOwned column?

The validation logic in processFileRows applies Math.max(0, Number(r.NumberOwned)) to every row. This coercion forces any negative value to 0, preventing invalid inventory states from entering the database.

How does the import feature handle files that are not CSV or Excel formats?

The component uses the browser's FileReader API, which reads raw bytes regardless of extension, but the subsequent XLSX.read call expects a supported spreadsheet format. If the file is malformed or an unsupported type, the parser throws an exception that is caught by the global try...catch block, displaying the localized error message t('errorProcessingExcel') to the user.

Can importing a CSV remove cards from my collection?

Yes. The import logic interprets the Collected boolean flag. When Collected is false and the card already exists in the user's collection, the system queues that cardId for deletion via deleteCardMutation.mutateAsync. This allows users to synchronize their digital collection with an external spreadsheet by marking cards as uncollected.

What type of feedback does the user receive during a large import operation?

The component provides real-time progress updates through React state. As each row processes, it updates progressMessage with the current card name and increments numberProcessed. This renders a live indicator showing which card is being handled and how many have been completed, preventing the UI from appearing frozen during lengthy operations.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →