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.

A quick note for beginners: this hook runs right after the initial render, making it a natural place for setup logic.

By integrating the React useEffect hook into your component logic, you can make your app dynamic, reactive to changes, and properly structured for cleanup. In many JavaScript projects, useEffect becomes the bridge between your UI and asynchronous logic.


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.

Developers often store display values or behavior flags in a state variable, which useEffect can respond to automatically.

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. This is sometimes called an empty dependency array, which many developers use when mimicking componentDidMount.

Some components also use this space to update the document title for user feedback.


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. If you trigger updates by calling setState, React schedules a new render, which may cause related effects to run again.


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. Developers refer to this process as effect cleanup, which keeps components stable across repeated executions.

Effects that rely on styling changes sometimes pair with CSS tweaks to update the UI.


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.

When handling state changes or responding to prop changes, this behavior becomes essential.

Be precise with dependencies to avoid issues like the React useEffect unexpectedly called error. Developers sometimes rely on structured markup in HTML when visualizing how effects influence layout.


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. When the new state is computed, the state changes trigger a rerender cycle.

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


Related Hooks and Concepts

This section collects hooks and patterns closely tied to useEffect, especially in modern frontend applications.

Applications built with ReactJS often involve tracking component renders, which helps identify where optimization can prevent unnecessary re-renders. Tools like useCallback allow developers to memoize a callback, while useMemo caches computed values to avoid wasteful recalculation.

Some projects include reusable logic extracted into a custom hook for clarity. In cases where access to DOM nodes or persistent values is needed, developers reach for useRef. These ideas apply across both functional and class components and improve everyday workflows when composing React components.


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. When testing example flows, developers sometimes schedule actions with setTimeout() to simulate delayed responses.


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. Many developers organize effects based on use cases that naturally break apart into smaller functional blocks.


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.