Skip to the solution
MediumFrontend6 min read

Explain the purpose of useEffect.

364 views

The problem

Question presented to candidate: "What is useEffect for β€” and, just as importantly, what is it not for?"

What a strong answer should cover:

  • The modern framing: an effect synchronises a component with an external system β€” the network, a subscription, the DOM, a timer. It is not a general-purpose lifecycle hook.
  • It runs after render and after the browser paints, so it never blocks the visible update.
  • The dependency array decides when it re-runs; the returned cleanup undoes the previous run.
  • The most valuable half of the answer: when not to use one. Derived values belong in render; user-action responses belong in event handlers.
  • Deriving state in an effect costs an extra render pass and leaves a moment where the UI shows the stale value.
  • Effects are an escape hatch from the React paradigm β€” the React docs categorise them that way deliberately.
  • Common wrong uses: transforming data for display, resetting state on prop change (use key), and doing work that belongs in a submit handler.
  • useLayoutEffect as the pre-paint exception; useEffectEvent for non-reactive logic inside an effect.

Clarifying questions expected:

  • "Is this synchronising with something outside React, or transforming data we already have?" β€” that single question decides whether an effect belongs at all.

Code / implementation expected: Yes β€” an effect that genuinely synchronises, beside a derived value that should not be one.

  • useEffect
  • side effects
  • lifecycle
  • hooks

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

6 min read Β· 8 sections

Try it in the playground

Open any example in the playground to run and change it.

ReactAn effect that genuinely synchronises, beside two that should not exist
import { useState, useEffect, useMemo } from "react";

const ALL = ["apple", "banana", "cherry", "date", "elderberry", "fig"];

// ❌ ANTI-PATTERN: derived state in an effect. Renders once with the stale
// value, then the effect sets state and forces a second render.
function WrongDerived({ query }) {
  const [results, setResults] = useState([]);
  const [renders, setRenders] = useState(0);
  useEffect(() => {
    setResults(ALL.filter((f) => f.includes(query)));
  }, [query]);
  useEffect(() => { setRenders((r) => r + 1); }, [results]);
  return <Line label="via effect (extra pass)" items={results} />;
}

// βœ… Just compute it. One render, never stale.
function RightDerived({ query }) {
  const results = useMemo(() => ALL.filter((f) => f.includes(query)), [query]);
  return <Line label="computed in render" items={results} />;
}

// βœ… A REAL effect: synchronising with something outside React. There is an
// external system (the document title) that must be started, kept matching,
// and restored on teardown.
function TitleSync({ query }) {
  useEffect(() => {
    const previous = document.title;
    document.title = query ? "search: " + query : "no search";
    return () => { document.title = previous; };
  }, [query]);
  return <p style={{ fontSize: 13, color: "#666" }}>document.title is synced to the query</p>;
}

function Line({ label, items }) {
  return (
    <p style={{ margin: "4px 0" }}>
      <code style={{ display: "inline-block", minWidth: 200 }}>{label}</code>
      {items.length ? items.join(", ") : "(none)"}
    </p>
  );
}

// βœ… Resetting state on a prop change WITHOUT an effect: the key prop.
function Editor({ docId }) {
  const [text, setText] = useState("");
  return (
    <div>
      <input
        value={text}
        placeholder={"editing doc " + docId}
        onChange={(e) => setText(e.target.value)}
      />
    </div>
  );
}

export default function App() {
  const [query, setQuery] = useState("a");
  const [docId, setDocId] = useState(1);

  return (
    <div style={{ padding: 24, fontFamily: "system-ui", lineHeight: 1.6 }}>
      <input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="filter" />
      <WrongDerived query={query} />
      <RightDerived query={query} />
      <TitleSync query={query} />

      <hr />
      <h4 style={{ margin: "0 0 8px" }}>Reset state with key, not an effect</h4>
      {/* Changing the key remounts Editor, so its text state resets by itself. */}
      <Editor key={docId} docId={docId} />
      <p>
        <button onClick={() => setDocId((d) => d + 1)}>Open the next document</button>
      </p>
      <p style={{ color: "#666", fontSize: 13 }}>
        Type in the editor, then open the next document β€” the field clears with
        no effect and no manual reset, because React remounted the component.
      </p>
    </div>
  );
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 61 of 119 in React.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All React.js questions
Up nextMedium

useFormStatus & useActionState β€” modern form handling in React 19

Continue