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

> Learn how the TCG Pocket Collection Tracker parses and validates CSV card data using xlsx, React Query, and robust error handling for seamless collection management.

- Repository: [Marcel Panse/tcg-pocket-collection-tracker](https://github.com/marcelpanse/tcg-pocket-collection-tracker)
- Tags: how-to-guide
- Published: 2026-03-06

---

**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`](https://github.com/marcelpanse/tcg-pocket-collection-tracker/blob/main/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:

```typescript
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`](https://github.com/marcelpanse/tcg-pocket-collection-tracker/blob/main/frontend/src/types/index.ts):

```typescript
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:

```typescript
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`:

```typescript
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:

```typescript
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:

```typescript
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:

```typescript
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`](https://github.com/marcelpanse/tcg-pocket-collection-tracker/blob/main/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`](https://github.com/marcelpanse/tcg-pocket-collection-tracker/blob/main/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.