Emily Chen·
Fed it a 400-line class component from 2019 and got hooks, typed props, and a list of behavior changes to verify before merging
Converts a legacy React class component to a typed function component with hooks, and explicitly lists every place where behavior could differ.
Legacy Class Component → Hooks + TypeScript
Convert the following legacy React class component to a modern function component with hooks and TypeScript. The goal is a faithful conversion first, cleanup second — do not silently change behavior.
Component code:
{{component_code}}
Project conventions: {{conventions}}
Requirements:
1. **Props and state**: define explicit interfaces. No `any`. If a prop's type can't be inferred from usage, use `unknown` and add a TODO comment explaining what to check.
2. **Lifecycle mapping**: convert componentDidMount/DidUpdate/WillUnmount to useEffect with correct dependency arrays. For every effect, add a one-line comment stating which lifecycle it replaces.
3. **Known trap check**: explicitly check for these conversion traps and report each one:
- setState callbacks (second argument) that assumed synchronous update
- this.state reads immediately after setState
- componentDidUpdate logic comparing prevProps/prevState that needs a ref or extra state in hooks
- class fields or instance variables that must become useRef, not state
- event handlers relying on `this` binding
4. **Cleanup pass** (separate, after the faithful version): memoization only where a measurable re-render problem exists — no reflexive useCallback on everything.
Output format:
- The converted component, fully typed
- **Behavior diff table**: what might behave differently | why | how to test it manually
- Anything you could NOT convert confidently, as questions
If the component is doing too much for one function component, say so and propose a split — but still deliver the single-component conversion.
Ergebnisse
## Converted component
```tsx
interface OrderTableProps {
customerId: string;
pageSize?: number;
onRowClick: (orderId: string) => void;
}
interface OrderRow {
id: string;
total: number;
status: "open" | "shipped" | "cancelled";
placedAt: string;
}
export function OrderTable({ customerId, pageSize = 25, onRowClick }: OrderTableProps) {
const [orders, setOrders] = useState<OrderRow[]>([]);
const [page, setPage] = useState(0);
const [loading, setLoading] = useState(false);
// was: instance field this._pollTimer — timer handle, never rendered → ref
const pollTimer = useRef<ReturnType<typeof setInterval> | null>(null);
// was: componentDidUpdate prevProps.customerId comparison
const prevCustomerId = useRef(customerId);
// replaces componentDidMount + componentWillUnmount (polling)
useEffect(() => {
pollTimer.current = setInterval(() => refetch(customerId, page), 30_000);
return () => { if (pollTimer.current) clearInterval(pollTimer.current); };
}, [customerId, page]);
// replaces componentDidUpdate: reset page when customer changes
useEffect(() => {
if (prevCustomerId.current !== customerId) {
prevCustomerId.current = customerId;
setPage(0);
}
}, [customerId]);
// ... fetch + render omitted for brevity, unchanged logic
}
```
## Behavior diff table
| What might differ | Why | How to verify |
|---|---|---|
| Page reset on customer change now happens in an effect (one render later) | Class version reset synchronously in componentDidUpdate before re-render commit | Switch customers rapidly; confirm no flash of old orders on the new customer |
| `setState({ page }, this.refetch)` callback removed | Hooks have no setState callback; refetch now driven by the effect's `[customerId, page]` deps | Click next-page fast 3×; network tab should show one request per page, no duplicates |
| Poll interval now restarts on page change | Original timer closed over stale page (a live bug — it always polled page 0) | Decide: keep the fix (recommended) or replicate the bug for a faithful diff |
| `this.handleRowClick` bind removed | Function component closures replace `this` binding | Row clicks still fire with correct orderId |
## Open questions
1. `this.props.store` was read once in componentDidMount but `store` isn't in propTypes — is this a legacy context injection? I typed it as `unknown` with a TODO.
2. The catch block swallowed fetch errors silently. I preserved that, but recommend surfacing a toast — say the word and I'll add it.
3. This component fetches, polls, paginates, and renders. Suggested split: `useOrderPolling` hook + presentational table. Faithful single-component version delivered as requested.
Modell: Cursor
35 Likes19 SavesScore: 24
2 Kommentare
Lena Fischer·
The behavior diff table should be standard practice for every refactor PR honestly.
Luca Brunner·
Nice touch catching the stale-closure polling bug and asking whether to keep it faithful. That question matters in review.
