# How the `useGeneration` Hook Manages the Generation Lifecycle in Modly

> Discover how the Modly useGeneration hook manages your 3D generation lifecycle. Learn about job creation, progress polling, and cancellation for a seamless workflow.

- Repository: [lightningpixel/modly](https://github.com/lightningpixel/modly)
- Tags: internals
- Published: 2026-08-20

---

**The `useGeneration` hook orchestrates the complete lifecycle of a 3-D generation job in Modly through three coordinated concerns: job creation and upload, polling with progress updates, and cancellation with reset capabilities.**

The `useGeneration` hook serves as the central nervous system for 3-D mesh generation in the Modly application. Located at [`src/shared/hooks/useGeneration.ts`](https://github.com/lightningpixel/modly/blob/main/src/shared/hooks/useGeneration.ts), it bridges React components with the FastAPI backend, managing state transitions from initial upload through final mesh delivery. This hook demonstrates a production-ready pattern for handling long-running asynchronous operations with cancellation support.

## Hook Initialization and Dependencies

The hook begins by extracting state and API functions from two critical sources. From the **Zustand** `appStore`, it retrieves the current job state, update methods, and mesh history management. From `useApi`, it obtains the three API functions that communicate with the backend.

```typescript
const { currentJob, setCurrentJob, updateCurrentJob, generationOptions,
        selectedImageData, pushMeshUrl, clearMeshHistory } = useAppStore()
const { generateFromImage, pollJobStatus, cancelJob } = useApi()

```

This dependency injection pattern (lines 5-8 in [`useGeneration.ts`](https://github.com/lightningpixel/modly/blob/main/useGeneration.ts)) ensures the hook remains testable and decoupled from direct API implementation details.

## Starting a Generation Job

The `startGeneration` function (lines 11-28) initiates the lifecycle through a carefully sequenced five-step process:

1. **Reset cancellation state** — Creates a fresh `AbortController` and clears any previous mesh history via `clearMeshHistory()`.
2. **Initialize job record** — Constructs a `GenerationJob` object with status `uploading` and stores it in the global `appStore`.
3. **Trigger backend upload** — Calls `generateFromImage` from `useApi` to begin server-side processing.
4. **Handle mid-upload cancellation** — If the user cancels during upload, immediately calls `cancelJob` and clears UI state.
5. **Begin polling** — Transitions status to `generating` and invokes `pollUntilDone(jobId)`.

```typescript
export function GenerateButton({ imagePath }: { imagePath: string }) {
  const { currentJob, startGeneration, cancelGeneration, reset } = useGeneration()

  return (
    <>
      {currentJob?.status === 'generating' ? (
        <button onClick={cancelGeneration}>Cancel</button>
      ) : (
        <button onClick={() => startGeneration(imagePath)}>Generate</button>
      )}
      {currentJob?.status === 'error' && (
        <div>
          <p>Error: {currentJob.error}</p>
          <button onClick={reset}>Try again</button>
        </div>
      )}
    </>
  )
}

```

## The Polling Loop: pollUntilDone

The heart of the generation lifecycle resides in `pollUntilDone` (lines 61-94), which implements a robust polling mechanism with four distinct status handlers:

- **`cancelled`** — Clears the UI state and terminates the loop.
- **`done`** — Sets `progress: 100`, pushes the final mesh URL via `pushMeshUrl`, and exits.
- **`error`** — Records the error message and terminates polling.
- **`running` / `pending`** — Updates intermediate progress values including optional `step` descriptions.

The loop executes every **1000 milliseconds** using `setTimeout`, checking the `cancelledRef` flag before each iteration. This cooperative cancellation pattern prevents race conditions between user-initiated cancellation and in-flight status requests.

```typescript
export function GenerationProgress() {
  const { currentJob } = useGeneration()
  if (!currentJob) return null

  return (
    <div>
      <p>Status: {currentJob.status}</p>
      <p>Progress: {currentJob.progress}%</p>
      {currentJob.step && <p>Step: {currentJob.step}</p>}
    </div>
  )
}

```

## Cancellation and Abort Handling

The `cancelGeneration` function (lines 96-102) implements immediate cooperative cancellation through two synchronized mechanisms:

- **Flag-based cancellation** — Sets `cancelledRef.current = true`, which the polling loop inspects on its next iteration.
- **Request abortion** — Signals the `AbortController` to terminate any in-flight `fetch` requests.

When the polling loop detects the cancellation flag, it proactively calls `cancelJob` to notify the backend, then clears the UI state. This ensures server resources are freed even if the user abandons the operation mid-generation.

## Reset: Returning to Idle State

The `reset` function provides a simple escape hatch: it calls `setCurrentJob(null)` to clear the current job from the `appStore`, immediately returning all consuming components to their idle state. Unlike cancellation, reset does not communicate with the backend—it purely affects client-side state.

## State Management Architecture

All mutations flow through the **Zustand** store defined in [`src/shared/stores/appStore.ts`](https://github.com/lightningpixel/modly/blob/main/src/shared/stores/appStore.ts). This centralized state management ensures:

- Components re-render automatically when job status changes.
- Mesh history persists across generation sessions via `pushMeshUrl` and `clearMeshHistory`.
- Generation options remain synchronized between the UI and active jobs.

The hook never mutates local React state directly—all updates route through `setCurrentJob` and `updateCurrentJob` store methods.

## API Integration Layer

The `useApi` hook ([`src/shared/hooks/useApi.ts`](https://github.com/lightningpixel/modly/blob/main/src/shared/hooks/useApi.ts)) encapsulates three FastAPI endpoints:

| Endpoint | Function | Purpose |
|----------|----------|---------|
| `POST /generate/from-image` | `generateFromImage` | Upload image and create job |
| `GET /generate/status/:id` | `pollJobStatus` | Retrieve current job status and progress |
| `POST /generate/cancel/:id` | `cancelJob` | Request server-side cancellation |

This abstraction allows `useGeneration` to remain agnostic of HTTP implementation details while maintaining type safety across the API boundary.

## Summary

- **`startGeneration`** creates jobs, clears history, uploads images, and initiates polling (lines 11-28).
- **`pollUntilDone`** repeatedly checks status every second, handling `cancelled`, `done`, `error`, and progress states (lines 61-94).
- **`cancelGeneration`** uses `cancelledRef` and `AbortController` for cooperative cancellation that cleans up both client and server state (lines 96-102).
- **`reset`** clears the current job locally without backend communication.
- **Zustand** `appStore` serves as the single source of truth for all generation state.

## Frequently Asked Questions

### How does `useGeneration` handle network failures during polling?

The hook treats any polling failure as an error condition. When `pollJobStatus` throws or returns an unexpected response, the polling loop captures the error message via `updateCurrentJob({ status: 'error', error: message })` and terminates. The UI can then display the error and offer a reset option.

### Can multiple generation jobs run simultaneously?

No—`useGeneration` manages a singleton `currentJob`. Starting a new generation calls `clearMeshHistory()`, wiping previous results. For concurrent generations, the hook would require architectural changes to track a job map rather than a single job reference.

### What prevents memory leaks from the polling interval?

The `pollUntilDone` loop uses `setTimeout` rather than `setInterval`, ensuring only one pending timer exists at any moment. The `AbortController` and `cancelledRef` pattern guarantees the loop exits cleanly on unmount or cancellation, preventing orphaned timers.

### How does cancellation synchronize between client and server?

The client sets a local flag immediately for responsive UI feedback. The next polling iteration detects this flag and calls `cancelJob`, which sends a cancellation request to the FastAPI backend. The server then updates the job status to `cancelled`, which the subsequent poll confirms before clearing state.