DEV Community

Cover image for You Might Not Need useEffect Anymore in React
Chittaranjan Nayak for Smartters

Posted on

You Might Not Need useEffect Anymore in React

_useEffect_ became the most frequently used and, at the same time, the most frequently misused hook in React since its release. It was used to do everything: data retrieval, state synchronization, event listening, and even lifecycle.

However, with the development of React, its best practices change. The introduction of React 19, Server Components, Server Actions, and other developments such as the React Compiler (Forget) are leading the ecosystem towards a more declarative, reactive and server-focused paradigm.

We will deconstruct in this blog:

  • Why useEffect can be more troublesome than helpful?
  • What the modern React provides as alternatives that are cleaner?
  • Practical cases in which useEffect can now be eliminated
  • When is useEffect necessary? useEffect (Img Source)

What Replaces useEffect in Modern React?

Why Replace It?

useEffect is called after render and this makes:

  • Extra re-renders
  • Improper layouts and bad user experience
  • Race conditions and problems that are difficult to debug
  • Compulsory logic that violates the declarative nature of React

Common useEffect Use Cases and Their Replacements

1. Fetching Data from an API

Old way (Client Component):

const [posts, setPosts] = useState([]); 

useEffect(() => { 
  fetch("/api/posts") 
    .then(res => res.json()) 
    .then(setPosts); 
}, []); 
Enter fullscreen mode Exit fullscreen mode

Modern way (Server Component in Next.js):

import { getPosts } from "@/lib/posts"; 

export default async function PostsPage() { 
  const posts = await getPosts(); 

  return ( 
    <ul> 
      {posts.map(p => <li key={p.id}>{p.title}</li>)} 
    </ul> 
  ); 
} 
Enter fullscreen mode Exit fullscreen mode

2. Form Submission and API Calls

Old way:

const handleSubmit = async () => { 
  const res = await fetch("/api/contact", { 
    method: "POST", 
    body: JSON.stringify(formData), 
  }); 
}; 
Enter fullscreen mode Exit fullscreen mode

Modern way (Server Actions):

// serverActions.ts 
"use server"; 
export async function sendContact(formData: FormData) { 
  await saveToDatabase(formData); 
} 
 <form action={sendContact}> 
  <input name="name" /> 
  <button type="submit">Send</button> 
</form> 
Enter fullscreen mode Exit fullscreen mode

3. Syncing State to LocalStorag

Old way:

useEffect(() => { 
  localStorage.setItem("theme", theme); 
}, [theme]); 
Enter fullscreen mode Exit fullscreen mode

Modern way:

import { useLocalStorage } from "react-use"; 

const [theme, setTheme] = useLocalStorage("theme", "light"); 
Enter fullscreen mode Exit fullscreen mode

4. Listening to Scroll or Resize Events

Old way:

useEffect(() => { 
  const handler = () => console.log(window.innerWidth); 
  window.addEventListener("resize", handler); 
  return () => window.removeEventListener("resize", handler); 
}, []); 
Enter fullscreen mode Exit fullscreen mode

Modern way:

import { useWindowSize } from "react-use"; 

const { width, height } = useWindowSize(); 
Enter fullscreen mode Exit fullscreen mode

5. Derived State or Value Syncing

Old way:

useEffect(() => { 
  setFullName(`${firstName} ${lastName}`); 
}, [firstName, lastName]); 
Enter fullscreen mode Exit fullscreen mode

Modern way (direct expression):

const fullName = `${firstName} ${lastName}`;
Enter fullscreen mode Exit fullscreen mode

Modern way (Signals):

import { signal, computed } from "@preact/signals-react"; 

const firstName = signal("Ayushi"); 
const lastName = signal("Singh"); 
const fullName = computed(() => `${firstName.value} ${lastName.value}`); 
Enter fullscreen mode Exit fullscreen mode

When You Still Need useEffect

  • Subscribing to external services (e.g., WebSocket, Firebase) 
  • Manually integrating third-party DOM libraries (like Chart.js) 
  • Logging, analytics, performance monitoring (on mount/unmount)
  • Event listeners not handled by React (e.g., global escape key) 

Signs You're Misusing useEffect

  • You’re fetching data on every render
    Solution: Move to Server Component 

  • You’re syncing two states 
    Solution: Use derived state / signal

  • You have a lot of useEffect with setState
    Solution: You may be recreating lifecycle patterns

  • You’re debugging stale closures or missing deps
    Solution: Rethink your design

Conclusion

useEffect is not deprecated, but it is not the first tool you have anymore. React 19 and Next.js 14 allow you to express how you want data to be fetched declaratively, Server Actions, and reactive primitives that address most of what useEffect was used to address, in a cleaner, more efficient way.

Write less code. Ship UI faster. Let React handle effects where it makes sense.


Frequently Asked Questions

1. Is useEffect still necessary in React 19?

A. Yes, but only for specific use cases like subscribing to external services, handling non-React events, or integrating third-party libraries. With React 19 and features like Server Components and Server Actions, many common useEffect patterns can now be replaced with cleaner, more declarative solutions.

2. What can I use instead of useEffect in Next.js 14?

A. In Next.js 14, you can replace many useEffect cases with Server Components, Server Actions, and the new React Compiler (Forget). These tools handle data fetching, side effects, and local syncing more efficiently without extra renders or complexity.

3. Why is useEffect considered bad or misused in React?

A. useEffect runs after every render, which can cause race conditions, performance issues, and unexpected behaviors. It often breaks the declarative nature of React. That’s why modern React encourages alternatives that align better with its core design principles.


Want to stay updated with more such latest tech news or need help building your next digital product?
Connect with Smartters on LinkedIn or visit our Website.
Let’s turn ideas into impactful solutions.

Top comments (0)