ESC

Type to search the knowledge base.

Side Effects in Frontend Code

Identify I/O and mutations outside return values — where effects belong in UI apps, React rules, and how to test around them.

beginner3 min read
  • javascript
  • side-effects

A side effect is anything a function does besides returning a value from its inputs: writing the DOM, fetching, logging, timers, storage, global mutation. Frontend apps are made of effects — the skill is where they live and how you keep the rest pure enough to reason about.

Catalog of common effects

Effect Example
Network fetch, WebSocket
Storage localStorage, IndexedDB, cookies
DOM el.focus(), imperative scroll
Scheduling setTimeout, rAF
Randomness/time Math.random, Date.now
Module state push to a singleton cache
function total(cart) {
  analytics.track('total_computed'); // effect hidden in “pure” name
  return cart.lines.reduce((s, l) => s + l.price, 0);
}

Structure that scales

UI event / framework lifecycle
        ↓
  command / effect boundary  (fetch, write storage)
        ↓
  pure domain functions      (pricing, validation, view-model map)
        ↓
  setState / render
// pure
export function canCheckout(cart, inventory) {
  return cart.items.every((i) => (inventory[i.sku] ?? 0) >= i.qty);
}

// effectful orchestration
export async function onCheckoutClick(cart, api) {
  const inventory = await api.getInventory(); // effect
  if (!canCheckout(cart, inventory)) {
    return { ok: false, reason: 'stock' };
  }
  await api.placeOrder(cart); // effect
  return { ok: true };
}

React mental model

  • Render should be pure with respect to props/state.
  • Event handlers are the right place for user-triggered effects.
  • useEffect syncs with external systems after paint (subscriptions, imperative DOM).
useEffect(() => {
  const id = setInterval(poll, 10_000);
  return () => clearInterval(id); // cleanup is part of effect hygiene
}, []);

Putting fetch only in render without a gate is an effect in the wrong phase — Strict Mode double-invoke will expose it.

Why isolate effects

  1. Tests — pure functions need no MSW; orchestration tests mock api.
  2. Reuse — same pricing rules in web and native.
  3. Concurrency — pure calc can rerun; effects need idempotency/abort.

Idempotency and cleanup

useEffect(() => {
  const ac = new AbortController();
  load(id, { signal: ac.signal })
    .then(setData)
    .catch((e) => {
      if (e.name !== 'AbortError') setErr(e);
    });
  return () => ac.abort();
}, [id]);

Effects that register listeners/timers without cleanup are SPA leaks.

Logging and analytics

Still effects. Prefer explicit calls at boundaries (track('purchase', payload)) over sprinkling inside reducers — reducers stay pure for time-travel/debug.

Interview answer (out loud)

“Side effects change something outside the return value — network, DOM, storage, time. I keep transforms pure and push effects to handlers, effects hooks, or service modules. Cleanup and abort matter for subscriptions and fetch. Hidden effects inside helpers make testing and Strict Mode painful.”

Module load effects

// module side effect on import — hard to test, order-sensitive
analytics.init();

Prefer explicit init() called from the app entry so tests can import modules without launching telemetry. Same rule for registering window listeners at module top level.

Further reading

Related guides