All checklists

React code review checklist

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.

Oscar Estudillo
Updated
  1. Use asynchronous act
  2. Re-enter a Transition after await
  3. Keep render calculations idempotent
  4. Do not use React.cloneElement
  5. Do not mutate external values during render
  6. Keep side effects out of render
  7. Avoid updates during render
  8. Prefer useEffect for non-layout work
  9. Associate every input with a label
  10. Do not suppress the react-hooks/exhaustive-deps linter rule
  11. Use callback refs for dynamic node lists

11 rules to check in every React pull request

Revix reviews React pull requests against 487 rules. This page shows a short list we picked from them. Each rule has a bad example, a good example and a source. We check each rule against its source and update the list every month. Rules marked as flagged by Revix have come up in real reviews.

Use asynchronous act

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

Avoid

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

Prefer

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

Flagged by Revix in real reviews · Deprecation · All current versions · Source: 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

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

Prefer

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

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

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

Prefer

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

Flagged by Revix in real reviews · Best practice · All current versions · Source: 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

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

Prefer

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

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

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

Prefer

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

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

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

Prefer

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

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

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

Prefer

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

Flagged by Revix in real reviews · Performance · All current versions · Source: React Performance tracks

Prefer useEffect for non-layout work

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

Avoid

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

Prefer

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

Flagged by Revix in real reviews · Performance · All current versions · Source: 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

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

Prefer

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

Flagged by Revix in real reviews · Best practice · All current versions · Source: <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

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

Prefer

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

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

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

Prefer

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

Deprecated React APIs to flag in review

These rules catch code that still works today but leans on an API that React has deprecated or replaced.

  1. Use asynchronous act
  2. Do not use React.cloneElement

How Revix applies these rules in pull requests

Revix matches these rules against the code each pull request changes. When a comment rests on one, it names the rule, so the author can read the reasoning and the source instead of taking the bot's word for it.

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

revixappdevBot

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

📚 Rule (React): Use asynchronous act

How the comment reads on a GitHub pull request when a change breaks the first rule on this page.

Reviews run natively on GitHub, GitLab and Bitbucket.

Revix also learns your team's own rules from the code you already merged. Start a free trial and see them on your next pull request.