React’s useEffect hook is one of the most important tools in a React developer's toolbox. It allows you to perform side effects in function components—like fetching data, listening to events, or updating the DOM.

In this article, we’ll explore how useEffect works, why it's needed, and how to use it correctly—with real examples and best practices.


🧠 What is a side effect?

A side effect is any operation that interacts with the outside world or causes a change outside the current function's scope.

Examples include:

  • Fetching data from an API
  • Subscribing to or cleaning up an event listener
  • Writing to local storage
  • Setting timers

In React, side effects should not run during rendering—that’s where useEffect comes in.


⚙️ Basic syntax

useEffect(() => {
  // Side effect logic here
}, [dependencies]);

  • The first argument is a function (effect).
  • The second is a dependency array.

✅ Useeffect without dependencies

useEffect(() => {
  console.log('Component mounted');
});

⚠️ This runs after every render. Not recommended unless necessary.


📦 Useeffect on component mount (run once)

useEffect(() => {
  console.log('Component mounted');

  return () => {
    console.log('Component unmounted');
  };
}, []);

✅ The empty array means the effect runs once after the initial render—like componentDidMount.

Also useful for cleanup logic when the component is removed.


🌐 Fetching data with useeffect

import { useEffect, useState } from 'react';

function UsersList() {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    async function fetchUsers() {
      const res = await fetch('/api/users');
      const data = await res.json();
      setUsers(data);
    }

    fetchUsers();
  }, []);

  return (
    <ul>
      {users.map(u => (
        <li key={u.id}>{u.name}</li>
      ))}
    </ul>
  );
}

📝 Tips:

  • Use async functions inside the effect.
  • Always provide a dependency array to avoid unnecessary requests.

🔁 Useeffect with dependencies

useEffect(() => {
  console.log(`The value is: ${value}`);
}, [value]);

This effect runs every time value changes.

You can include multiple dependencies:

useEffect(() => {
  console.log('User or token changed');
}, [user, token]);


🧹 Cleanup functions

Cleanup helps avoid memory leaks—like when using timers or event listeners.

useEffect(() => {
  const interval = setInterval(() => {
    console.log('Tick');
  }, 1000);

  return () => clearInterval(interval);
}, []);

🚨 Common mistakes to avoid

❌ Forgetting dependencies

✅ Always include all variables used in the effect in the dependency array (or use eslint-plugin-react-hooks)

❌ Using async directly in useEffect

✅ Instead, define an async function inside the effect and call it

❌ Triggering infinite loops

✅ Double-check that the effect doesn’t update its own dependencies unintentionally


🧰 Best practices checklist

✔️ Use useEffect for data fetching, subscriptions, and timers

✔️ Clean up with return () => {} inside the effect

✔️ Always use a dependency array

✔️ Break effects into smaller ones when they do different things

✔️ Use custom hooks to reuse effect logic across components


🧠 Conclusion

useEffect is a powerful and essential part of writing React applications. It helps you manage side effects in a clean, predictable way. By understanding how dependencies work and how to avoid common pitfalls, you can build responsive and robust components.

Practice is key—start using useEffect in real components to solidify your understanding.