Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/millionco/react-doctor/llms.txt

Use this file to discover all available pages before exploring further.

These rules catch common mistakes with useState, useEffect, and related hooks that lead to bugs, performance issues, or unnecessary re-renders.

Rules

Severity: error
Rule ID: react-doctor/no-derived-state-effect
Detects state that is derived from props or other state inside useEffect. This pattern is almost always wrong.Why it’s bad:
  • Creates an extra render cycle
  • State updates happen after render instead of during
  • Can cause infinite loops if dependencies aren’t perfect
Bad:
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    setUser(fetchUser(userId));
  }, [userId]);
}
Good:
function UserProfile({ userId }) {
  const user = fetchUser(userId); // Compute during render
}
Or use a key to reset component state:
<UserProfile key={userId} />
Severity: error
Rule ID: react-doctor/no-fetch-in-effect
Prevents using fetch() directly inside useEffect. Manual data fetching leads to race conditions, missing loading states, and no caching.Why it’s bad:
  • Race conditions when component unmounts
  • No automatic deduplication or caching
  • Missing loading/error states
  • Waterfall requests
Bad:
useEffect(() => {
  fetch('/api/data').then(r => r.json()).then(setData);
}, []);
Good:
// Use a data fetching library
const { data } = useSWR('/api/data', fetcher);

// Or a server component in Next.js
async function Page() {
  const data = await fetch('/api/data');
  return <UI data={data} />;
}
Severity: warn
Rule ID: react-doctor/no-cascading-set-state
Flags useEffect containing 3 or more setState calls. This indicates state that should be managed together.Why it’s bad:
  • Multiple setState calls = multiple re-renders
  • Hard to keep state synchronized
  • Suggests missing abstraction
Bad:
useEffect(() => {
  setName(data.name);
  setEmail(data.email);
  setAge(data.age);
}, [data]);
Good:
// Use useReducer for related state
const [state, dispatch] = useReducer(reducer, initialState);

useEffect(() => {
  dispatch({ type: 'SET_USER', payload: data });
}, [data]);
Severity: warn
Rule ID: react-doctor/no-effect-event-handler
Detects useEffect that runs only when a specific value changes and contains a single if statement. This is usually an event handler in disguise.Bad:
useEffect(() => {
  if (isOpen) {
    trackModal();
  }
}, [isOpen]);
Good:
function handleOpen() {
  setIsOpen(true);
  trackModal();
}
Severity: warn
Rule ID: react-doctor/no-derived-useState
Catches useState initialized directly from a prop value. If the prop changes, state won’t update.Bad:
function Form({ initialValue }) {
  const [value, setValue] = useState(initialValue);
  // initialValue changes won't update state!
}
Good:
// If you need to sync with prop changes:
function Form({ initialValue }) {
  const [value, setValue] = useState(initialValue);
  
  useEffect(() => {
    setValue(initialValue);
  }, [initialValue]);
}

// Or just derive it:
function Form({ value }) {
  return <input value={value} />;
}
Severity: warn
Rule ID: react-doctor/prefer-useReducer
Suggests using useReducer when a component has 5+ useState calls for related state.Bad:
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [age, setAge] = useState(0);
const [address, setAddress] = useState('');
const [phone, setPhone] = useState('');
Good:
const [state, dispatch] = useReducer(reducer, {
  name: '',
  email: '',
  age: 0,
  address: '',
  phone: ''
});
Severity: warn
Rule ID: react-doctor/rerender-lazy-state-init
Enforces lazy initialization for useState when initialized with a function call. The function runs on every render even though only the first result is used.Bad:
const [state, setState] = useState(expensiveComputation());
// expensiveComputation() runs on EVERY render
Good:
const [state, setState] = useState(() => expensiveComputation());
// Only runs once on mount
Severity: warn
Rule ID: react-doctor/rerender-functional-setstate
Requires using functional updates when the new state depends on the old state to avoid stale closures.Bad:
setCount(count + 1);
Good:
setCount(prev => prev + 1);
Severity: error
Rule ID: react-doctor/rerender-dependencies
Catches object literals and array literals in dependency arrays. These create new references every render, causing the effect to run infinitely.Bad:
useEffect(() => {
  doSomething();
}, [{ id: userId }]); // New object every render!
Good:
useEffect(() => {
  doSomething();
}, [userId]); // Primitive value