# React Class To Functional

> Convert React class components to functional components with hooks

- Skill: `cockroachdb/react-class-to-functional` (Agent Skill)
- Install (CLI): `npx skillmds@latest add cockroachdb/react-class-to-functional`
- Raw SKILL.md: https://api.skillmd.com/api/skills/cockroachdb/react-class-to-functional/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: cockroachdb (https://skillmd.com/u/cockroachdb)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/cockroachdb/react-class-to-functional

---


# React Class to Functional Component Converter

Convert the React class component at `$ARGUMENTS` to a functional component using React hooks.

## Conversion Rules

### State
- `this.state = { ... }` → individual `useState` hooks
- `this.setState({ key: value })` → setter function
- `this.setState(prev => ...)` → functional update form
- Mutable objects (Set/Map) must be cloned on update:
  ```typescript
  setExpandedRows(prev => { const next = new Set(prev); next.add(key); return next; });
  ```

### Lifecycle → useEffect

| Class | Functional |
|-------|-----------|
| `componentDidMount` | `useEffect(fn, [])` |
| `componentDidUpdate` | `useEffect(fn)` or `useEffect(fn, [deps])` |
| `componentWillUnmount` | `useEffect(() => cleanup, [])` |
| mount + unmount | single `useEffect` with cleanup return |

### Other Mappings
- `React.createRef()` → `useRef(null)`
- `static contextType` → `useContext(MyContext)`
- Bound methods / arrow class methods → local function inside functional component
- Non-reactive instance variables (`this.timer`) → `useRef`
- `createSelector` (reselect) → `useMemo` with explicit dependency array

### Props and Defaults
- `this.props.x` → destructured props
- `static defaultProps` → default parameter values
- **Critical**: mark defaultProps as optional (`?`) in the interface:
  ```typescript
  interface Props { sortSetting?: SortSetting; }
  function Component({ sortSetting = defaultValue }: Props) { ... }
  ```

### setState with Callback
`this.setState(update, callback)` has no direct hook equivalent. Use ref + useEffect:
```typescript
const pendingRef = useRef(false);
const prevRef = useRef(value);

const onUpdate = useCallback(() => {
  pendingRef.current = true;
  setValue(newValue);
}, []);

useEffect(() => {
  if (pendingRef.current && prevRef.current !== value) {
    pendingRef.current = false;
    doCallback();
  }
  prevRef.current = value;
}, [value, doCallback]);
```

## Generic Components

- `class MyTable extends SortedTable<Row> {}` → `const MyTable = SortedTable<Row>;`
- Exported type aliases require the props interface to also be exported (else "cannot be named" build errors)
- TypeScript may fail to infer generics with spread props — add explicit type params: `<SortedTable<Row> {...props} />`

## Workflow

1. Read the file and summarize: state vars, lifecycle methods, refs, methods, generics, files extending this class
2. Before converting each method, verify it's actually called — delete dead code, don't convert it
3. Write the converted component, preserving imports/comments/exports and adding hook imports
4. Search for and update dependent files that extend the class

## Verification Checklist

- [ ] All `this.` references removed
- [ ] Hooks follow Rules of Hooks (top level, consistent order)
- [ ] TypeScript types preserved; exports unchanged
- [ ] defaultProps marked optional in interface
- [ ] Exported type aliases have their props interfaces exported
- [ ] Files extending the class updated to type alias pattern
- [ ] No dead code converted (every `useCallback` is actually called)
- [ ] No orphaned helpers (if logic was inlined, delete the original)
- [ ] No unused destructured props
- [ ] No failed intermediate attempts left behind
- [ ] Within the appropriate directory with `package.json`, run ESLint (fix) and Tests

