DEV Community

Alejandro
Alejandro

Posted on

Part 1: Why I Rarely Use useEffect Anymore (and what I use instead)

When I started learning React, I thought useEffect was the solution to almost everything. To derive state, to fetch data, to synchronize props or calculate a value, the answer for all this was useEffect.
After working on larger applications, i realized most of those effects weren't needed at all.
Today, I probably write 80% fewer useEffects than I did a few years ago. Not because it's a bad hook but because most problems have simpler solutions.
This article is the first part of my Modern React Patterns series, where I go over patterns that worked fine in small demos but became painful in production.


What useEffect is actually for

The React documentation describes effects as a way to synchronize your component with external systems.
That means things like:

  • network requests
  • WebSockets
  • timers
  • browser APIs
  • subscriptions
  • third-party libraries

If your effect isn't interacting with something outside React, there's a good chance you don't need one.


1. Don't use useEffect for

One of the most common examples I still see is this:

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

It works but React now renders twice. The process that now is doing is the following:
render --> effect runs --> state changes --> render again
and there's no reason for that.

Instead i do this:

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

2. Don't synchronize props into state

const [user, setUser] = useState(props.user);
useEffect(() => {
    setuser(props.user);
}, [props.user]);
Enter fullscreen mode Exit fullscreen mode

Most of the times this creates two sources of truth. Eventually one of them gets updated and the other doesn't. Unless you intentionally want a local editable copy, just use the prop directly. It's much simpler

function prodile({user}){
    return <h2>{user.name}</h2>;
}
Enter fullscreen mode Exit fullscreen mode

3. Don't calculate values inside an effect

I've seen code like this many times.

const[filteredUsers, setFilteredUsers]=useStat([]);

useEffect(() => {
    setFilteredUsers(
        users.filter(user=> user.active)
    );
}, [users]);
Enter fullscreen mode Exit fullscreen mode

That's unnecessary state, i'ts way better:

const filteredUsers=users.filter(
    user=> user.active
);
Enter fullscreen mode Exit fullscreen mode

If the computation is actually expensive:

const filteredUsers=useMemo(() => {
    return users.filter(user=> user.active):
}, [users]);
Enter fullscreen mode Exit fullscreen mode

Notice that useMemo is an optimization, not a replacement for every calculation.


4. Debugging is one of the few places where I still use effects a lot

When debugging state updates I often do this:

useEffect(() => {
    console.log(user);
}, [user]);
Enter fullscreen mode Exit fullscreen mode

It's incredibly useful while debugging but before merging the code, delete it.


5. Fetching data with useEffect isn't always the best choice anymore

Years ago almost every React project looked like this:

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

Nowadays we have much better alternatives. Libraries like TanStack Query or SWR solve things that you eventually end up building yourself:

  • caching
  • retries
  • background refetching
  • loading state
  • error state
  • deduplication And instead of all of this, you can:
const {data, isLoading}= useQuery({
    queryKey: ["users"]
    queryFn: getUsers
});
Enter fullscreen mode Exit fullscreen mode

That's less code and you get fewer bugs and better developer experience.

If you're interested in API reliability, I wrote another article about common integration mistakes:

"Building Reliable API integrations in Modern Web Applications"


6. Effects ofter hide architecture problems

Something I've noticed over time is whenever I find myself writing lots of effects indife the same component and it's usually because the component is trying to do too much. For example:

  • fetching
  • filtering
  • sorting
  • formatting
  • validation
  • event handling
  • rendering

and everything together. Splitting responsabilities into smaller hooks or components ofter removes half of those effects automatically.


When you SHOULD use useEffect

None of this means "never use useEffect". There are plenty of legitimate use cases to use it, and let me tell some of them.
WebSocket connections:

useEffect(() => {
    const socket = new WebSocket(url);
    return() => socket.close();
}, []);
Enter fullscreen mode Exit fullscreen mode

Timers:

useEffect(() => {
    const id= setInterval(fetchData, 5000);
    return() => clearInterval(id);
}, []);
Enter fullscreen mode Exit fullscreen mode

Browser APIs:

useEffect(() => {
    window.addEventListener("resize", onResize);
    return() => {
        window.removeEventListener("resize", onResize);
    };
}, []);
Enter fullscreen mode Exit fullscreen mode

Third-party libraries:

useEffect(() => {
    const chart= new Chart(canvas);
    return()=> chart.destroy();
}, []);
Enter fullscreen mode Exit fullscreen mode

These are exactly the kinds of external systems effects were designed for.


My personal rule

Whenever I'm about to write an effect, I stop for a second and ask myself if am I synchronizing with an external system or am I compensating for my component design. That question alone has removed a surprising amount of unnecessary code from my projects.


Conclusion

useEffect isn't bad. It's just much easier to overuse than most React hooks. Today I try to use:

  • derived values instead of derived state
  • props instead of synchronized state
  • TanStack Query for server state
  • custom hooks for reusable logic
  • effects only when I actually need to synchronize with something outside React

And you can get fewer renders, less state, fewer bugs and components that are easier to understand months later.


Next article in this series

part 2: Stop Fetching Data Inside useEffect


Thanks for reading!! If you're looking for help with an existing project or need someone to solve a tricky frontend/backend issue, you can also find my freelance profiles through my Dev.to profile.

Top comments (0)