# How ReClip Frontend Manages State: Centralized Mutable State in Vanilla JavaScript

> Discover how ReClip frontend manages state using centralized mutable state in vanilla JavaScript. Learn how currentFormat and cardData drive UI updates efficiently.

- Repository: [Avery Gan/reclip](https://github.com/averygan/reclip)
- Tags: internals
- Published: 2026-09-05

---

**ReClip uses a centralized state management pattern with two top-level mutable variables—`currentFormat` and `cardData`—that drive all UI updates through pure rendering functions.**

ReClip is a single-page application built with vanilla JavaScript embedded directly in [`templates/index.html`](https://github.com/averygan/reclip/blob/main/templates/index.html). Unlike modern frameworks that use reactive state libraries, ReClip frontend state management relies on simple global variables mutated directly by event handlers and async operations. This architecture makes the data flow explicit and easy to trace through the core video download workflow.

## State Architecture Overview

The entire application state lives in the global scope of the browser's JavaScript execution context. According to the source code in [`templates/index.html`](https://github.com/averygan/reclip/blob/main/templates/index.html), ReClip declares two critical variables at the start of the script:

```javascript
let currentFormat = 'video';  // Controls MP4 vs MP3 preference
let cardData = [];            // Array holding all URL card states

```

This minimal state footprint keeps the application lightweight while supporting complex asynchronous workflows including metadata fetching, format selection, and download progress tracking.

## Core State Variables

### `currentFormat`: Global Format Preference

The `currentFormat` variable tracks whether the user prefers **MP4** (video) or **MP3** (audio) downloads. Defined at line 11 of [`templates/index.html`](https://github.com/averygan/reclip/blob/main/templates/index.html), this string defaults to `'video'` and influences both the UI rendering logic and the payload sent to the backend download endpoint.

### `cardData`: The Card State Registry

`cardData` is an array where each element represents a single URL submission. Every card object contains:

- **url**: The submitted YouTube or streaming URL
- **status**: A finite state machine value (`loading`, `info-error`, `ready`, `downloading`, `done`, `error`)
- **metadata**: Title, thumbnail, duration, and available format IDs
- **selectedFormatId**: The quality/format chosen by the user
- **jobId**: Backend identifier for active downloads
- **filename**: Final output name (populated when `status` becomes `done`)

Initialized as an empty array at line 12, `cardData` grows dynamically as users paste URLs and click the **Fetch** button.

## State Initialization and the Fetch Flow

When a user clicks **Fetch**, the `go()` function in [`templates/index.html`](https://github.com/averygan/reclip/blob/main/templates/index.html) transforms raw URLs into structured state entries. The function first parses the input textarea, then creates a new card entry for each URL:

```javascript
async function go() {
  const urls = parseUrls(document.getElementById('urls').value);
  // ...
  for (let i = 0; i < urls.length; i++) {
    const url = urls[i];
    const idx = cardData.length;
    cardData.push({ url, status: 'loading' });   // State: loading
    renderCard(idx);                              // UI reflects loading
    
    const res = await fetch('/api/info', /* ... */);
    const data = await res.json();
    
    if (data.error) {
      cardData[idx] = { /* ... */, status: 'info-error', error: data.error };
    } else {
      cardData[idx] = { /* ... */, status: 'ready', /* metadata */ };
    }
    renderCard(idx);                              // UI reflects ready/error
  }
}

```

The state mutation follows a strict pattern: update `cardData[idx]`, then call `renderCard(idx)` to synchronize the DOM.

## State Transitions and Download Lifecycle

ReClip implements a finite state machine within each card object. Transitions occur explicitly through function calls that modify `cardData[idx].status`:

**1. Ready to Downloading**
When the user clicks a **Download** button, `dlCard(idx)` updates the status immediately before issuing the network request:

```javascript
function dlCard(idx) {
  const c = cardData[idx];
  c.status = 'downloading';      // State transition
  renderCard(idx);                // Show downloading spinner
  
  const res = await fetch('/api/download', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      url: c.url,
      format: currentFormat,    // Uses global state
      format_id: c.selectedFormatId,
      title: c.title || '',
    }),
  });
  // ...
}

```

**2. Downloading to Done or Error**
The `pollCard(idx)` function implements active polling against `/api/status/<job_id>`, updating state every second until completion:

```javascript
function pollCard(idx) {
  const c = cardData[idx];
  const iv = setInterval(async () => {
    const res = await fetch(`/api/status/${c.jobId}`);
    const data = await res.json();
    
    if (data.status === 'done') {
      clearInterval(iv);
      c.status = 'done';         // State: done
      c.filename = data.filename;
      renderCard(idx);
      saveCard(idx);
    } else if (data.status === 'error') {
      clearInterval(iv);
      c.status = 'error';        // State: error
      c.error = data.error;
      renderCard(idx);
    }
  }, 1000);
}

```

Because state mutations happen synchronously before `renderCard()` calls, the UI never displays stale data during these transitions.

## Rendering as a Pure Function of State

The `renderCard(idx)` function acts as a pure transformation of the current state into HTML. It inspects `cardData[idx].status` and builds the appropriate DOM fragment:

- **`loading`**: Displays a skeleton placeholder
- **`info-error`**: Shows error messaging with retry options
- **`ready`**: Renders thumbnail, title, duration, and quality selection chips
- **`downloading`**: Displays progress indicator with cancel option
- **`done`**: Provides the download link for the processed file
- **`error`**: Shows failure details from the backend

This architecture ensures that any change to `cardData` immediately reflects in the DOM once `renderCard()` executes, creating a predictable, debuggable relationship between state and UI.

## Format Selection State

The `currentFormat` variable demonstrates how global preferences propagate through the application. When users toggle between video and audio modes, only this single variable changes. Both the thumbnail generation logic (`thumbHtml`) and the download request payload read from this centralized source, ensuring consistency across all cards without individual synchronization.

## Summary

- **Centralized State**: All application data lives in two global variables (`currentFormat` and `cardData`) defined in [`templates/index.html`](https://github.com/averygan/reclip/blob/main/templates/index.html).
- **Explicit Mutations**: State changes occur through direct assignment in functions like `go()`, `dlCard()`, and `pollCard()`.
- **Status-Driven UI**: The `renderCard()` function implements a pure mapping from state values (`loading`, `ready`, `downloading`, `done`, `error`) to HTML structures.
- **Backend Synchronization**: Frontend state updates depend on endpoints defined in [`app.py`](https://github.com/averygan/reclip/blob/main/app.py), including `/api/info` for metadata and `/api/status/<job_id>` for download progress.
- **No External Dependencies**: ReClip manages complex async workflows without React, Vue, or Redux, relying instead on vanilla JavaScript and disciplined state mutation patterns.

## Frequently Asked Questions

### How does ReClip track download progress without a frontend framework?

ReClip uses a polling mechanism in the `pollCard()` function that queries `/api/status/<job_id>` every second. The function updates `cardData[idx].status` directly and calls `renderCard(idx)` to refresh the UI. This imperative approach avoids the overhead of virtual DOM diffing while maintaining accurate progress tracking.

### Where is the application state stored in ReClip?

All state resides in the global scope of [`templates/index.html`](https://github.com/averygan/reclip/blob/main/templates/index.html) as two mutable variables: `let currentFormat = 'video'` and `let cardData = []`. This simplified architecture eliminates the need for state management libraries but requires careful handling to avoid memory leaks during rapid card creation and deletion.

### What triggers UI updates in ReClip?

UI updates are triggered explicitly by calling `renderCard(idx)` immediately after modifying `cardData[idx]`. For example, when the `go()` function receives metadata from `/api/info`, it updates the card object and invokes `renderCard(idx)` in the same execution block, ensuring the DOM stays synchronized with the JavaScript state.

### Can ReClip handle multiple simultaneous downloads?

Yes. Because `cardData` is an array where each index maintains independent state, users can fetch metadata and download multiple videos concurrently. Each card tracks its own `status`, `jobId`, and `filename` without interfering with other entries, though the backend [`app.py`](https://github.com/averygan/reclip/blob/main/app.py) must manage the actual concurrency limits for processing.