React Forum
Blog & Guide Responses
Jan 21, 2023 · 04:00 AM

Synchronizing with Effects - discussion (2023-01-21)

Started by Mina Calder
externalreact.deveffectssubscriptionstimerscleanupevidencestrict mode

Synchronizing with Effects is one of the few pages that treats effects as a real boundary with real failure modes (cleanup, stale closures, double-invoke). I'm curious what conventions people use so effects stay observable and don't secretly become the place where state correctness is "fixed" after render.

Read the article

What evidence do you render for effect-driven behavior (status lanes, last run/cleanup timestamps, counters)? How do you prevent stale closure bugs without turning every dependency list into a ritual? When do you choose a remount boundary over an effect cleanup path?

Comments (12)

Back to latest
Marisol Vega
Jan 25, 2023 · 12:12 PM

We render lastEffectRunAt and lastCleanupAt for the scary effects.

It made debugging ten times easier because you can tell if the effect actually ran.

Dmitri Kline
Feb 07, 2023 · 03:03 AM

Stale closure bugs got better when we introduced a useLatest helper and kept effects dependent on stable keys only:

tsx
function useLatest<T>(value: T) {
  const ref = useRef(value);
  ref.current = value;
  return ref;
}

function useInterval(fn: () => void, ms: number) {
  const latest = useLatest(fn);
  useEffect(() => {
    const id = setInterval(() => latest.current(), ms);
    return () => clearInterval(id);
  }, [ms]);
}
Keira Santos
Feb 20, 2023 · 06:40 PM

Counterpoint: useLatest can hide data flow if it's used everywhere.

We only use it for genuine outside-world sync (timers/subscriptions), not for internal state coordination.

Mina Calder
Feb 24, 2023 · 06:05 AM

Agree. It's a tool, not a default. If you can model it as explicit state transitions, do that instead.

Effects should stay boundary-specific.

Grace McCoy
Mar 10, 2023 · 11:11 AM

Remount boundaries replaced a lot of "reset in cleanup" effects for us.

If the identity changed (docId/sessionSlug), remounting was the most honest thing.

Jasper Nguyen
Mar 28, 2023 · 04:44 AM

We made effect cleanup observable by rendering an activeSubscriptions counter behind a flag.

When the counter only ever goes up, you know you leaked something.

Nina Kapoor
Apr 15, 2023 · 10:22 PM

A misunderstanding I had: Strict Mode double run isn't "React being weird"; it's telling you your effect isn't idempotent.

Once I accepted that, cleanup bugs got easier to fix.

Owen Price
May 06, 2023 · 06:18 AM

We split effects into two categories: outside-world sync and internal orchestration.

The second category usually becomes explicit state transitions once you see enough bugs.

Benji Rios
May 29, 2023 · 07:10 PM

Docs tie-in: effects are expensive mostly because they're invisible unless you render evidence.

Related: Testing and Debugging and Performance and Rendering.

Lina Ortega
Jun 18, 2023 · 03:03 AM

Counterpoint: sometimes you do need an effect to coordinate internal state when integrating legacy code.

We keep those effects behind a feature flag and we record them as ledger entries so the behavior is explainable.

Dmitri Kline
Jun 21, 2023 · 01:30 PM

That seems like a good compromise: accept the effect, but force it to be observable and temporary.

If it isn't recorded, it becomes an invisible state machine.

Amina Farouk
Jul 12, 2023 · 08:08 AM

We also keep effect bodies tiny and push computation into pure helpers.

If the effect is 50 lines, dependency correctness is impossible to reason about.

Tomas Rivera
Aug 06, 2023 · 04:40 PM

Practical evidence: store and render effectName + lane + lastRunAt.

Once you can see those, timing bugs stop being mysterious.

Salma Qureshi
Sep 23, 2023 · 12:21 PM

The thread takeaway for me: effects are fine when they're boundaries, but boundaries need observable signals or they turn into ghosts.