Infinite loop in useEffect due to incorrect dependencies

Loading

Preventing Infinite Loops in useEffect with Proper Dependencies

A common React mistake is creating infinite loops in useEffect by specifying incorrect dependencies, causing the effect to repeatedly trigger itself.

The Problem (Infinite Loop)

function UserProfile() {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    // ❌ Triggers infinite re-renders
    setLoading(true);
    fetchUser().then(data => {
      setUser(data);
      setLoading(false);
    });
  }, [user, loading]); // Problematic dependencies

  return <div>{loading ? 'Loading...' : user?.name}</div>;
}

Why this causes an infinite loop:

  1. Effect runs when user or loading changes
  2. Effect modifies both user and loading
  3. Triggers another effect run
  4. Cycle repeats indefinitely

Correct Solutions

1. Empty Dependency Array (Mount Only)

useEffect(() => {
  setLoading(true);
  fetchUser().then(data => {
    setUser(data);
    setLoading(false);
  });
}, []); // ✅ Runs only once on mount

2. Proper State Management

const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);

const fetchData = useCallback(async () => {
  setLoading(true);
  const data = await fetchUser();
  setUser(data);
  setLoading(false);
}, []);

useEffect(() => {
  fetchData();
}, [fetchData]); // ✅ Stable dependency

3. Using Refs for Tracking

const isMounted = useRef(false);

useEffect(() => {
  if (!isMounted.current) {
    setLoading(true);
    fetchUser().then(data => {
      setUser(data);
      setLoading(false);
    });
    isMounted.current = true;
  }
}, []); // ✅ Runs only once

4. For Dependent Effects

const [userId, setUserId] = useState(1);

useEffect(() => {
  let ignore = false;

  setLoading(true);
  fetchUser(userId).then(data => {
    if (!ignore) {
      setUser(data);
      setLoading(false);
    }
  });

  return () => { ignore = true }; // ✅ Cleanup for rapid ID changes
}, [userId]); // ✅ Only re-run when userId changes

Common Infinite Loop Scenarios

  1. State setters in dependency array:
   const [count, setCount] = useState(0);

   useEffect(() => {
     setCount(count + 1);
   }, [count, setCount]); // ❌ Infinite updates
  1. New objects/arrays as dependencies:
   const [data, setData] = useState([]);

   useEffect(() => {
     setData([...data, newItem]);
   }, [data]); // ❌ New array reference each time
  1. Functions without useCallback:
   const fetch = () => { /*...*/ };

   useEffect(() => {
     fetch();
   }, [fetch]); // ❌ New function each render

Best Practices

  1. Be precise with dependencies – Only include what’s actually used
  2. Memoize objects/functions with useMemo/useCallback when needed in deps
  3. Consider derived state – Often better than effects
  4. Use the exhaustive-deps ESLint rule to catch issues
  5. Break complex effects into smaller, focused effects

Advanced Patterns

1. Effect Dependencies with Refs

const [count, setCount] = useState(0);
const countRef = useRef(count);

useEffect(() => {
  countRef.current = count; // Track latest value
}, [count]);

useEffect(() => {
  const timer = setInterval(() => {
    console.log(countRef.current); // Always current
  }, 1000);

  return () => clearInterval(timer);
}, []); // No count dependency needed

2. Custom Hook for Data Fetching

function useFetch(url) {
  const [data, setData] = useState(null);

  useEffect(() => {
    let ignore = false;

    fetch(url)
      .then(res => res.json())
      .then(data => !ignore && setData(data));

    return () => { ignore = true };
  }, [url]); // Only re-run when URL changes

  return data;
}

3. Combining Multiple States

const [state, setState] = useState({
  user: null,
  loading: false,
  error: null
});

useEffect(() => {
  let isMounted = true;

  setState(prev => ({...prev, loading: true}));

  fetchUser()
    .then(user => isMounted && setState({user, loading: false, error: null}))
    .catch(error => isMounted && setState(prev => ({...prev, loading: false, error})));

  return () => { isMounted = false };
}, []); // Single effect

Leave a Reply

Your email address will not be published. Required fields are marked *