REACT

React useEffect Hook: Syntax, Usage, and Examples

The React useEffect hook lets you synchronize a component with external systems. It replaces lifecycle methods like componentDidMount, componentDidUpdate, and componentWillUnmount from class-based components. In function components, you use useEffect to perform side effects such as data fetching, subscriptions, DOM updates, and timers.

By integrating the React useEffect hook into your component logic, you can make your app dynamic, reactive to changes, and properly structured for cleanup.


How React useEffect Works

React calls the useEffect function after every render by default. You pass it a function that performs the effect and, optionally, a dependency array that determines when the effect runs again.

Basic syntax:

useEffect(() => {
  // code to run after render
});

This runs after every render. To control when it runs, you can add a dependency array as a second argument.

Controlled execution:

useEffect(() => {
  console.log("Runs only once");
}, []);

The empty array means it runs only once after the first render.


You Can Handle Side Effects

Side effects include operations that reach outside React’s rendering process, such as:

  • API requests
  • Setting timers
  • Event listeners
  • Manual DOM manipulation
  • Logging

Example: Fetching data when the component mounts

useEffect(() => {
  fetch("/api/user")
    .then(res => res.json())
    .then(data => setUser(data));
}, []);

This runs once when the component is added to the DOM.


You Should Clean Up with useEffect

To prevent memory leaks, you can return a cleanup function. This cleanup runs before the component unmounts or before re-running the effect.

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

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

This is especially important for subscriptions and timers.


Dependency Array Explained

The dependency array controls when the effect runs. React compares values in the array after every render. If any value changes, React re-runs the effect.

useEffect(() => {
  console.log("Runs when count changes");
}, [count]);

An empty array runs the effect once. An array with dependencies runs the effect when those dependencies change. Omitting the array entirely causes the effect to run after every render.

Be precise with dependencies to avoid issues like the React useEffect unexpectedly called error, which often occurs due to state updates inside the effect.


Avoiding Infinite Loops in useEffect

An infinite loop can happen if you update state inside the effect without controlling the dependencies. For example:

useEffect(() => {
  setCount(count + 1); // Dangerous
});

This updates the state, which triggers another render, which re-runs the effect, leading to a loop.

Always use a dependency array to limit how often the effect runs.


useEffect vs useState in React

React useEffect and useState serve different purposes but often work together. useState holds and updates component state, while useEffect reacts to those changes.

Example:

const [searchTerm, setSearchTerm] = useState("");

useEffect(() => {
  fetchData(searchTerm);
}, [searchTerm]);

Here, useState stores the input value, and useEffect triggers an API call each time the term changes.

Understanding the difference between useEffect vs useState React logic improves control over component behavior.


You Can Use Async Functions in useEffect

The effect function itself cannot be marked async, but you can define and call an inner async function.

useEffect(() => {
  const fetchData = async () => {
    const res = await fetch("/api/data");
    const result = await res.json();
    setData(result);
  };

  fetchData();
}, []);

This pattern enables API calls and other asynchronous logic without breaking the rules of hooks.


Common useEffect Issues and Fixes

useEffect unexpectedly called

This usually happens when dependencies are missing or incorrect. Always declare every variable or function used inside the effect in the dependency array.

useEffect(() => {
  someCallback(); // Declare someCallback in dependencies
}, [someCallback]);

React strict mode can also call the effect twice on mount in development, which is expected and harmless.

useEffect called twice

In development, React runs useEffect twice to detect unsafe side effects. This doesn't happen in production, but you should avoid writing logic that causes problems if the effect runs more than once.

useEffect with an empty array

This is the equivalent of componentDidMount, and it’s safe for running setup logic like initial API calls.


Handling useEffect in Component Unmount

Cleanup logic helps avoid memory leaks when a component is removed from the DOM.

Example:

useEffect(() => {
  const handler = () => console.log("Window resized");
  window.addEventListener("resize", handler);

  return () => {
    window.removeEventListener("resize", handler);
  };
}, []);

This is essential for maintaining performance and avoiding bugs.


You Should Use Multiple useEffect Hooks

Each effect should do one thing. Using multiple useEffect hooks improves clarity and separation of concerns.

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

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

This keeps logic modular and easier to debug.


Using useEffect for Event Listeners

You can attach and detach custom events inside useEffect.

useEffect(() => {
  const handleScroll = () => console.log("Scrolled");
  window.addEventListener("scroll", handleScroll);

  return () => {
    window.removeEventListener("scroll", handleScroll);
  };
}, []);

This avoids duplicating listeners and keeps the UI efficient.


useEffect with React Context or Props

React useEffect also tracks changes in context or props. When a prop value changes, the effect re-runs.

useEffect(() => {
  console.log("User role changed:", role);
}, [role]);

This keeps components responsive to external changes.


Best Practices for useEffect in React

  • Keep effects small and focused
  • Declare all dependencies
  • Separate unrelated concerns into separate effects
  • Avoid modifying state conditionally inside useEffect
  • Use cleanup functions for subscriptions and timers

These practices lead to more maintainable and bug-free components.


The useEffect React hook provides a powerful way to synchronize function components with external systems. You can perform side effects like fetching data, subscribing to services, and cleaning up timers or event listeners.

Learn to Code in React for Free
Start learning now
button icon
To advance beyond this tutorial and learn React by doing, try the interactive experience of Mimo. Whether you're starting from scratch or brushing up your coding skills, Mimo helps you take your coding journey above and beyond.

Sign up or download Mimo from the App Store or Google Play to enhance your programming skills and prepare for a career in tech.

You can code, too.

© 2025 Mimo GmbH

Reach your coding goals faster