DEV Community

Anas Sheikh
Anas Sheikh

Posted on

Stop Syncing State With useEffect: 4 Bugs That Disappear When You Delete the Effect

Here is a pattern I see in almost every React codebase I read:

const [fullName, setFullName] = useState('');

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

It works. It also renders twice every time a name changes, shows a stale value for one frame, and is a bug waiting to happen when someone adds a third dependency and forgets to list it.

The fix is to delete the effect. Here are four cases where that is the right move.

Why These Effects Cause Bugs

An effect runs after React has painted the screen. So any state you set inside it means:

  1. React renders with the old value and paints it.
  2. The effect runs and calls setState.
  3. React renders again with the new value.

The user can see step 1. That is the flicker. And every effect you add is another place where the dependency list can silently drift out of date.

Case 1: Derived Values

If you can calculate something from props or state you already have, do not store it:

// Before
const [fullName, setFullName] = useState('');
useEffect(() => {
  setFullName(firstName + ' ' + lastName);
}, [firstName, lastName]);

// After
const fullName = firstName + ' ' + lastName;
Enter fullscreen mode Exit fullscreen mode

No state, no effect, no extra render, and it can never be out of sync, because it is recalculated every time.

The same goes for filtering and sorting:

// Before: two sources of truth
const [visible, setVisible] = useState(items);
useEffect(() => {
  setVisible(items.filter((i) => i.active));
}, [items]);

// After
const visible = items.filter((i) => i.active);
Enter fullscreen mode Exit fullscreen mode

If the calculation is genuinely expensive, wrap it in useMemo. Measure first. For a few hundred items, plain code is fine.

Case 2: Resetting State When a Prop Changes

This is the one people struggle with most:

function Profile({ userId }: { userId: string }) {
  const [comment, setComment] = useState('');

  useEffect(() => {
    setComment(''); // reset when the user changes
  }, [userId]);

  return <textarea value={comment} onChange={(e) => setComment(e.target.value)} />;
}
Enter fullscreen mode Exit fullscreen mode

When userId changes, the old comment shows for one frame, then clears. If you have several pieces of state, you need one reset line for each, and you will forget one.

The fix is the key prop:

function ProfilePage({ userId }: { userId: string }) {
  return <Profile key={userId} userId={userId} />;
}

function Profile({ userId }: { userId: string }) {
  const [comment, setComment] = useState('');
  return <textarea value={comment} onChange={(e) => setComment(e.target.value)} />;
}
Enter fullscreen mode Exit fullscreen mode

When the key changes, React treats it as a different component: it throws away the old one, including all its state, and mounts a fresh one. Every piece of state resets, with no effect and no flicker. Remember this one, because it solves a whole category of "why is the old data still there" bugs.

Case 3: Logic That Belongs in an Event Handler

const [submitted, setSubmitted] = useState(false);

useEffect(() => {
  if (submitted) {
    sendOrder(cart);
    setSubmitted(false);
  }
}, [submitted, cart]);

return <button onClick={() => setSubmitted(true)}>Buy</button>;
Enter fullscreen mode Exit fullscreen mode

This uses an effect as a delayed event handler. It is hard to follow, and it can fire again if cart changes while submitted is still true.

Put the logic where the event happens:

function handleBuy() {
  sendOrder(cart);
}

return <button onClick={handleBuy}>Buy</button>;
Enter fullscreen mode Exit fullscreen mode

The rule of thumb: if something happens because the user did something, it belongs in an event handler. If it happens because the component appeared on screen, it can be an effect.

Case 4: Notifying a Parent

useEffect(() => {
  onChange(value);
}, [value, onChange]);
Enter fullscreen mode Exit fullscreen mode

This calls the parent after the render, so the parent and child briefly disagree, and it can trigger extra renders in both. Call the parent in the same handler that changes the value:

function handleChange(next: string) {
  setValue(next);
  onChange(next);
}
Enter fullscreen mode Exit fullscreen mode

Both updates are batched into one render.

So When Is an Effect Right?

Effects are for synchronizing with something outside React:

  • Subscribing to a browser event, a WebSocket or a timer
  • Starting and cleaning up a third-party widget
  • Sending an analytics event because a page was shown
  • Fetching data on mount (with a cleanup, so a slow response cannot overwrite a newer one)

That last point deserves care, because an effect that fetches can show old results if responses arrive out of order. I wrote about that race condition in a separate article about search inputs. If your library offers one, a data-fetching library or a framework's server data loading removes the need for the effect altogether.

Quick Checklist Before You Write an Effect

Question If yes
Can I calculate it from existing props or state? Calculate it during render
Am I resetting state when a prop changes? Use key
Does it happen because the user did something? Put it in the event handler
Am I telling the parent something changed? Call the parent in the handler
Am I connecting to something outside React? An effect is right

The Rule

An effect that only calls setState based on other state or props is almost always a bug waiting to happen. Compute it during render, reset with key, and handle events in handlers.

Quick audit: search your project for useEffect and read the body of each one. Count how many do nothing but set state. Each one is a candidate for deletion, and the component usually gets shorter and faster when you remove it.

If you build with Next.js and React and want a clean starting point, you can browse my templates on pixelanas.com.

Get the templates: https://pixelanas.gumroad.com

What is the strangest useEffect you have ever found in a codebase? Share it in the comments.


Anas, full-stack Next.js developer building SaaS products and premium templates. X: @ASheikh69751

Top comments (1)

Collapse
 
alexcodebytes profile image
Oleksandr •

What about the case where you need to sync state with an external store? Like a Zustand or Redux store? That's technically 'syncing state,' but it's also the legitimate use case for effects (or better yet, useSyncExternalStore). Would be curious to hear your take.