# Convert React Class Components to Hooks

Yes. An agent converts your React class components to function components with hooks, one per commit, runs the tests after each and opens a pull request.

[Run this on Rush](https://cloud.getrush.ai/run/convert-class-components-to-hooks/react/), in a Project that has your repository.

## What the agent does

In a React codebase like excalidraw/excalidraw, the agent starts by listing every class component, and the list is short. Searching packages and excalidraw-app for classes that extend React.Component or PureComponent finds three: App in packages/excalidraw/components/App.tsx, which the prompt puts out of scope, TopErrorBoundary in excalidraw-app/components/TopErrorBoundary.tsx, and Collab in excalidraw-app/collab/Collab.tsx. Reading each one decides what can be converted at all.

TopErrorBoundary cannot become a function component. It catches render errors with componentDidCatch, reports them through Sentry.captureException and renders an error screen, and React has no hook equivalent for an error boundary. The agent leaves it as a class and says so in the pull request, rather than adding a new dependency to wrap it.

Collab is the real work: a PureComponent of about 1,100 lines that keeps CollabState in this.state, private fields such as collaborators, followedBy and lastBroadcastedOrReceivedSceneVersion, a socket initialisation timer, and listeners set up in componentDidMount and removed in componentWillUnmount. It also hands itself to new Portal(this), and excalidraw-app/collab/Portal.tsx reads this.collab.state.username and calls this.collab.excalidrawAPI and this.collab.fileManager. So the conversion maps state to useState, the mutable fields to useRef, mount and unmount to a useEffect with cleanup, and gives Portal a small object with exactly the fields it reads, keeping the CollabAPI that componentDidMount publishes to collabAPIAtom identical.

Each converted component is its own commit, as the prompt asks, and after each the agent runs the repository's checks: yarn test:typecheck for tsc, yarn test:app --watch=false for Vitest, which includes excalidraw-app/tests/collab.test.tsx, and yarn test:code for ESLint with zero warnings. It also starts the app and opens a collaboration session in the sandbox's browser, because the tests cover undo and batched updates but not a second user joining a room.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "claude",
  "input": "Convert the class components in this app to function components with hooks, except the main App class in packages/excalidraw/components/App.tsx, which is out of scope. List every class component first. Leave error boundaries as classes and explain why. Keep behaviour identical: same props, same public API objects, same effects on mount and unmount. Where another module holds a reference to the component instance, give it an explicit object with only the fields it uses. Make one commit per component, run yarn test:typecheck, yarn test:app --watch=false and yarn test:code after each, start the app and try the converted feature in the browser, and open a pull request listing each component and what was checked.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    }
  ]
}
```

1. **Get an API key.** In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
2. **Send the request.** POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
3. **Read what came back.** Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

## What comes back

The pull request has one commit per converted component, so a reviewer can read Collab's conversion on its own, plus any small change it needed in a module that used the instance, such as Portal. The description lists every class component the agent found, which ones it converted, which it left as classes and why, and the result of typecheck, tests and lint after each commit, with a short note on what it tried by hand in the browser.

A reviewer checks the effects most carefully: every listener added on mount must be removed in the cleanup, every timer cleared, and nothing that was read from this.state at call time should now read a stale value captured in a closure. Collaboration is hard to cover with unit tests, so the reviewer should also open two windows on the branch and edit together before merging.

## Run it on every component left on classes

Start the run from a script with POST /o/{org}/sessions before you log off, and read the result the next day: the webhook on your key posts run.completed with the session_url, and the session carries the pull request and the full transcript. The sandbox stays parked, so a follow-up message such as asking it to split a commit picks up where it stopped.

In a codebase with many class components, run one session per directory or feature, so each pull request is a reviewable size and two runs never edit the same file.

## Why run it on Rush?

Converting a large class like Collab means many rounds of edit, typecheck, test and manual check in a browser, and the work is easy to leave half-done when the day ends. In a Rush sandbox the agent has its own copy of the repository, a browser to try the feature, and every yarn command kept with its output, so you can start it in the evening and pick up a finished pull request in the morning.

The one-commit-per-component rule is easy for an agent to keep and makes review much faster. Because each run is isolated, you can convert separate parts of the app in parallel and merge them in any order.

## Frequently asked questions

### Can every class component be converted to hooks?

No. Error boundaries need componentDidCatch or getDerivedStateFromError, which have no hook equivalent, so the agent keeps them as classes and explains it. In this example that applies to TopErrorBoundary.

### What if another module calls methods on the component instance?

The agent finds those callers, such as Portal taking the Collab instance in its constructor, and gives them an explicit object with only the fields they use. That keeps the caller working and makes the dependency visible.

### Will the tests catch a broken conversion?

They catch some problems, and the agent runs them after every commit. Lifecycle mistakes such as a listener not removed on unmount are easy to miss in tests, so it also starts the app and uses the feature, and the pull request says what was checked by hand.

### Why is the main App class out of scope?

App.tsx is the editor's core and much larger than the rest, so converting it is a project of its own. Keeping it out lets this pull request finish and be reviewed; you can start a separate run for it later.
