# React code review checklist

> React code review checklist of the mistakes Revix flags in real pull requests, from tests in a synchronous act() to side effects during render.

Canonical URL: https://www.revix.ai/checklists/react

React code review often misses bugs in tests, async updates and rendering. This checklist covers what Revix flags in real pull requests. Look for tests in a synchronous act(), state updates lost from a Transition after an await, and render code with a different result each run. Each rule shows the code to flag, the fix and its source.

Author: Oscar Estudillo · Updated: 2026-10-30

## 11 rules to check in every React pull request

### Use asynchronous act

Synchronous act may leave updates unflushed across scheduling boundaries, making tests unreliable.

**Avoid**

```tsx
act(() => {
  root.render(<App />);
});
```

**Prefer**

```tsx
await act(async () => {
  root.render(<App />);
});
```

Flagged by Revix in real reviews · Deprecation · All current versions · Source: [act](https://react.dev/reference/react/act)

### Re-enter a Transition after await

Without the nested call, the post-await update is scheduled as a regular update rather than as a Transition.

**Avoid**

```tsx
startTransition(async () => {
  await save();
  setPage('/done');
});
```

**Prefer**

```tsx
startTransition(async () => {
  await save();
  startTransition(() => setPage('/done'));
});
```

Flagged by Revix in real reviews · API usage · All current versions · Source: [React doesn't treat my state update after await as a Transition](https://react.dev/reference/react/useTransition#react-doesnt-treat-my-state-update-after-await-as-a-transition)

### Keep render calculations idempotent

React may run rendering multiple times or pause and resume it. Non-idempotent calculations can produce inconsistent or stale UI.

**Avoid**

```tsx
function Clock() { return <span>{new Date().toLocaleString()}</span>; }
```

**Prefer**

```tsx
const [time, setTime] = useState(() => new Date());
```

Flagged by Revix in real reviews · Best practice · All current versions · Source: [Components and Hooks must be pure](https://react.dev/reference/rules/components-and-hooks-must-be-pure)

### Do not use React.cloneElement

Continued use of cloneElement can cause fragile and harder-to-maintain code; newer patterns are more declarative.

**Avoid**

```tsx
React.cloneElement(child, {prop: value})
```

**Prefer**

```tsx
Use props composition or render props pattern instead of cloning elements.
```

Flagged by Revix in real reviews · Deprecation · All current versions · Source: [Legacy React APIs](https://react.dev/reference/react/legacy)

### Do not mutate external values during render

Persistent mutations are repeated when React renders again and can accumulate duplicated data or affect other components.

**Avoid**

```tsx
const items = []; function List({ friends }) { friends.forEach(f => items.push(f)); return items; }
```

**Prefer**

```tsx
function List({ friends }) { const items = []; friends.forEach(f => items.push(f)); return items; }
```

Flagged by Revix in real reviews · Anti-pattern · All current versions · Source: [Components and Hooks must be pure](https://react.dev/reference/rules/components-and-hooks-must-be-pure)

### Keep side effects out of render

React can render components multiple times, so an untracked side effect may run repeatedly or at an unexpected time.

**Avoid**

```tsx
function Panel() { analytics.track('panel-rendered'); return <PanelView />; }
```

**Prefer**

```tsx
function Panel() { useEffect(() => analytics.track('panel-shown'), []); return <PanelView />; }
```

Flagged by Revix in real reviews · Best practice · All current versions · Source: [Components and Hooks must be pure](https://react.dev/reference/rules/components-and-hooks-must-be-pure)

### Avoid updates during render

React documents cascading updates as a performance-regression pattern because work completed in the current render pass may be discarded.

**Avoid**

```tsx
function Panel() {
  if (needsRefresh) setReady(true);
  return <View />;
}
```

**Prefer**

```tsx
function Panel() {
  const ready = !needsRefresh;
  return <View ready={ready} />;
}
```

Flagged by Revix in real reviews · Performance · All current versions · Source: [React Performance tracks](https://react.dev/reference/dev-tools/react-performance-tracks)

### Prefer useEffect for non-layout work

React explicitly warns that useLayoutEffect can hurt performance and recommends useEffect when possible.

**Avoid**

```tsx
useLayoutEffect(() => { fetchUser(); }, []);
```

**Prefer**

```tsx
useEffect(() => { fetchUser(); }, []);
```

Flagged by Revix in real reviews · Performance · All current versions · Source: [useLayoutEffect](https://react.dev/reference/react/useLayoutEffect)

### Associate every input with a label

Without an association, screen readers do not announce the field's caption when it receives focus, and clicking the caption will not focus the input.

**Avoid**

```tsx
<label>Your age:</label><input id="age" />
```

**Prefer**

```tsx
<label htmlFor={ageId}>Your age:</label><input id={ageId} />
```

Flagged by Revix in real reviews · Best practice · All current versions · Source: [<input>](https://react.dev/reference/react-dom/components/input)

### Do not suppress the react-hooks/exhaustive-deps linter rule

The linter helps catch mistakes that are otherwise hard to detect. Ignoring it amounts to lying to React about effect dependencies, making behavior unpredictable.

**Avoid**

```tsx
useEffect(() => { doSomething(); }, []); // eslint-ignore-next-line react-hooks/exhaustive-deps
```

**Prefer**

```tsx
useEffect(() => { doSomething(); }, [dep1, dep2]); // all dependencies included and no lint suppression
```

Flagged by Revix in real reviews · Anti-pattern · All current versions · Source: [React useEffect documentation](https://react.dev/reference/react/useEffect#specifying-reactive-dependencies)

### Use callback refs for dynamic node lists

DOM structure changes can make selector-based lookup brittle or cause it to target the wrong nodes.

**Avoid**

```tsx
const listRef = useRef(null);
const item = listRef.current.querySelectorAll('li')[index];
```

**Prefer**

```tsx
ref={node => {
  nodes.current.set(item.id, node);
  return () => nodes.current.delete(item.id);
}}
```

Flagged by Revix in real reviews · Anti-pattern · All current versions · Source: [Manipulating the DOM with Refs](https://react.dev/learn/manipulating-the-dom-with-refs)
