[{"data":1,"prerenderedAt":339},["ShallowReactive",2],{"topic-react-hooks":3},{"framework":4,"topic":15,"subtopics":23},{"id":5,"description":6,"extension":7,"icon":8,"meta":9,"name":10,"order":11,"slug":8,"stem":12,"tier":13,"__hash__":14},"frameworks\u002Fframeworks\u002Freact.yml","React interview questions on hooks, components, state and rendering — the most-requested front-end framework in technical interviews.","yml","react",{},"React",2,"frameworks\u002Freact",1,"RA6wemU0xFHrA1ZKCABP1J7MireA3Bt_FCJMOuMgsm0",{"id":16,"description":17,"extension":7,"frameworkSlug":8,"meta":18,"name":19,"order":13,"slug":20,"stem":21,"__hash__":22},"topics\u002Ftopics\u002Freact-hooks.yml","useState, useEffect and the rules of hooks — the foundation of modern function-component React.",{},"Hooks","hooks","topics\u002Freact-hooks","jrLwNTdmn0GZbYOHuDjmphKf7fd5vx_B_TYSEX9Us1w",[24,184],{"id":25,"title":26,"body":27,"description":31,"difficulty":33,"extension":34,"framework":10,"frameworkSlug":8,"meta":35,"navigation":36,"order":13,"path":37,"questions":38,"related":177,"seo":178,"seoDescription":179,"stem":180,"subtopic":181,"topic":19,"topicSlug":20,"updated":182,"__hash__":183},"qa\u002Freact\u002Fhooks\u002Fusestate.md","Usestate",{"type":28,"value":29,"toc":30},"minimark",[],{"title":31,"searchDepth":11,"depth":11,"links":32},"",[],"easy","md",{},true,"\u002Freact\u002Fhooks\u002Fusestate",[39,43,48,52,56,60,65,69,73,77,81,85,89,93,97,101,105,109,113,117,121,125,129,133,137,141,145,149,153,157,161,165,169,173],{"id":40,"difficulty":33,"q":41,"a":42},"what-is-usestate","What does useState return?","`useState` returns an array of exactly two elements: the **current state\nvalue** for this render, and a **setter function** that schedules an update\nand triggers a re-render. You destructure them, and the `[value, setValue]`\nnaming convention is just that — a convention, not something React enforces.\n\n```jsx\nconst [count, setCount] = useState(0)\n\u002F\u002F     ▲ current value  ▲ setter that re-renders with the next value\n```\n\nA few things that trip people up in interviews:\n\n- The argument (`0` here) is only the **initial** value. On every render\n  after the first, React ignores it and hands you the latest stored value.\n- The setter has a **stable identity** — React guarantees it never changes\n  between renders, so it's safe to omit from `useEffect`\u002F`useCallback`\n  dependency arrays.\n- Calling the setter does **not** mutate `count` in the current scope; it\n  asks React to render again with a new value.\n",{"id":44,"difficulty":45,"q":46,"a":47},"async-state","medium","Why does state appear to be \"one render behind\"?","Because `count` is a **const captured by this render's closure**, not a live\nreference to a mutable box. Each render gets its own `count` constant frozen\nat the value it had when that render ran. Calling `setCount` schedules a\n*future* render with a new value — it cannot reach back and change the\n`count` you're currently looking at.\n\n```jsx\nfunction handleClick() {\n  console.log(count)   \u002F\u002F e.g. 0\n  setCount(count + 1)  \u002F\u002F schedules a render where count will be 1\n  console.log(count)   \u002F\u002F STILL 0 — same render, same frozen constant\n}\n```\n\nSo state isn't really \"behind\" — you're reading a snapshot. The new value\nbecomes visible only in the next render's function body. If you need the\nupdated value immediately after setting it, derive it locally\n(`const next = count + 1`) or read it in an effect that runs after the\nre-render.\n",{"id":49,"difficulty":45,"q":50,"a":51},"functional-update","When should you use a functional state update?","Use the functional form — `setCount(c => c + 1)` — whenever the next state is\n**derived from the previous state**, especially when several updates happen in\none event or across async boundaries. React passes the most up-to-date value\ninto your updater, so you never base a calculation on a stale snapshot.\n\n```jsx\n\u002F\u002F All three read the same stale `count`, so this adds 1, not 3\nsetCount(count + 1)\nsetCount(count + 1)\nsetCount(count + 1)\n\n\u002F\u002F Each updater receives the result of the previous one -> +3\nsetCount(c => c + 1)\nsetCount(c => c + 1)\nsetCount(c => c + 1)\n```\n\nRule of thumb: if the new value mentions the old value, prefer the function\nform. If you're setting an unrelated value (`setCount(0)`), the direct form\nis fine.\n",{"id":53,"difficulty":45,"q":54,"a":55},"lazy-init","What is lazy initial state?","If your initial value is expensive to compute, pass a **function** to\n`useState` instead of the value itself. React calls that function **only on\nthe first render** and ignores it afterwards. Passing the value directly would\nre-run the expensive computation on *every* render and then throw the result\naway.\n\n```jsx\n\u002F\u002F readFromLocalStorage() runs on every single render\nconst [items, setItems] = useState(readFromLocalStorage())\n\n\u002F\u002F runs once, on mount only\nconst [items, setItems] = useState(() => readFromLocalStorage())\n```\n\nWatch the distinction: `useState(expensive())` *calls* `expensive` every\nrender (its return value is the argument), whereas `useState(expensive)` \u002F\n`useState(() => expensive())` hands React the function to call once.\n",{"id":57,"difficulty":45,"q":58,"a":59},"object-state","How do you update an object in state?","Unlike `this.setState` in class components, the `useState` setter **replaces**\nthe value — it does not shallow-merge. To change one field of an object you\nmust spread the previous object yourself and override the field, returning a\n**new** object (mutating the existing one won't trigger a re-render because the\nreference is unchanged).\n\n```jsx\nconst [user, setUser] = useState({ name: 'Ada', age: 36 })\n\n\u002F\u002F new object, old fields preserved, name overridden\nsetUser(u => ({ ...u, name: 'Grace' }))\n\n\u002F\u002F mutates in place — same reference, React skips the re-render\nuser.name = 'Grace'\nsetUser(user)\n```\n\nFor deeply nested state this spreading gets verbose; that's often the signal\nto reach for `useReducer` or a state library.\n",{"id":61,"difficulty":62,"q":63,"a":64},"batching","hard","What is state batching?","Batching is React grouping multiple state updates into a **single re-render**\nfor performance, instead of re-rendering once per `setState` call. If you call\nthree setters in one click handler, React processes them together and renders\nonce.\n\n```jsx\nfunction handleClick() {\n  setA(1)\n  setB(2)\n  setC(3)\n  \u002F\u002F ONE re-render, not three\n}\n```\n\nBefore React 18, batching only happened inside React event handlers; updates\nin `setTimeout`, promises, or native event listeners each caused their own\nrender. **React 18's automatic batching** extends grouping to those async\ncontexts too. If you ever need to opt out and force a synchronous, separate\nrender, wrap the update in `flushSync` from `react-dom`.\n",{"id":66,"difficulty":45,"q":67,"a":68},"derived-state","Should you store computed\u002Fderived values in state?","Usually **no**. If a value can be calculated from existing props or state,\nderive it **during render** instead of duplicating it in `useState` — extra\nstate can drift out of sync and forces you to keep two things updated.\n\n```jsx\n\u002F\u002F redundant state that can desync\nconst [items, setItems] = useState([])\nconst [count, setCount] = useState(0)\n\n\u002F\u002F derive it\nconst [items, setItems] = useState([])\nconst count = items.length\n```\n\nOnly store something in state if it's genuinely independent input. \"You might\nnot need state\" is the React team's own guidance.\n",{"id":70,"difficulty":45,"q":71,"a":72},"lift-state-up","What does \"lifting state up\" mean?","When two components need to share or stay in sync over the same data, you move\nthe state to their **closest common parent** and pass it down as props plus a\nsetter callback. The parent becomes the single source of truth.\n\n```jsx\nfunction Parent() {\n  const [value, setValue] = useState('')\n  return (\n    \u003C>\n      \u003CInput value={value} onChange={setValue} \u002F>\n      \u003CPreview value={value} \u002F>\n    \u003C\u002F>\n  )\n}\n```\n\nIt's the standard fix for \"these siblings need the same data.\" When lifting gets\npainful across many levels, that's the signal to reach for Context or a store.\n",{"id":74,"difficulty":45,"q":75,"a":76},"usestate-vs-useref","When should you use useRef instead of useState?","Use `useState` for values that should **trigger a re-render** when they change.\nUse `useRef` for mutable values that should **persist across renders but NOT\ncause re-renders** — timer ids, previous values, DOM nodes.\n\n```jsx\nconst [count, setCount] = useState(0) \u002F\u002F UI depends on it -> re-render\nconst renders = useRef(0)             \u002F\u002F bookkeeping -> no re-render\nrenders.current++\n```\n\nChanging `ref.current` is invisible to React's render cycle. If the screen needs\nto reflect the value, it belongs in state; otherwise a ref avoids needless\nrenders.\n",{"id":78,"difficulty":62,"q":79,"a":80},"reset-state-key","How do you reset a component's state to its initial values?","The cleanest way is to change the component's **`key`**. React treats a new key\nas a brand-new component, unmounting the old instance (discarding its state) and\nmounting a fresh one — no manual reset code.\n\n```jsx\n\u003CProfile key={userId} userId={userId} \u002F>\n\u002F\u002F when userId changes, Profile remounts with fresh state\n```\n\nAlternatives are manually calling setters back to initial values, but `key` is\nidiomatic for \"start this subtree over.\" Avoid resetting state inside an effect\nby watching a prop — the `key` approach is simpler and bug-free.\n",{"id":82,"difficulty":62,"q":83,"a":84},"store-function-state","How do you store a function in state?","Because the `useState` setter treats a **function argument as an updater**, you\nmust wrap a function you want to *store* in another function — otherwise React\ncalls it instead of saving it.\n\n```jsx\n\u002F\u002F React invokes handleClick to compute the next state\nconst [fn, setFn] = useState(handleClick)\nsetFn(handleClick)\n\n\u002F\u002F wrap it so it's stored, not called\nconst [fn, setFn] = useState(() => handleClick)\nsetFn(() => handleClick)\n```\n\nThe same rule applies to lazy initialization. Storing functions in state is rare\n— usually a ref or just defining the function in render is cleaner.\n",{"id":86,"difficulty":33,"q":87,"a":88},"array-state-add","How do you add and remove items in array state?","Treat the array as **immutable** — never `push`\u002F`splice` the existing array\n(same reference -> no re-render). Build a new array with spread or `filter`.\n\n```jsx\n\u002F\u002F add\nsetItems(prev => [...prev, newItem])\n\u002F\u002F remove by id\nsetItems(prev => prev.filter(it => it.id !== id))\n\u002F\u002F insert at index\nsetItems(prev => [...prev.slice(0, i), newItem, ...prev.slice(i)])\n```\n\nUsing the functional updater (`prev =>`) keeps you correct when several updates\nbatch together.\n",{"id":90,"difficulty":45,"q":91,"a":92},"array-state-update","How do you update one object inside an array in state?","Map over the array, returning a **new object** for the matching item and the\noriginals for the rest. Mutating the found object in place won't re-render and\ncan corrupt previous renders.\n\n```jsx\nsetUsers(prev =>\n  prev.map(u => u.id === id ? { ...u, name: 'Ada' } : u)\n)\n```\n\nThe rule: new array **and** new object for whatever you change. For deeply\nnested updates this gets verbose — a signal to use `useReducer` or Immer.\n",{"id":94,"difficulty":62,"q":95,"a":96},"usestate-vs-usereducer","When should you choose useReducer over useState?","Reach for `useReducer` when state is **complex** (multiple sub-values that change\ntogether), when the **next state depends on intricate logic**, or when you want\nto **centralize update logic** in one tested function instead of scattering\nsetters.\n\n```jsx\nconst [state, dispatch] = useReducer(reducer, initial)\ndispatch({ type: 'increment', by: 2 })\n```\n\n`useState` is best for simple, independent values. A heuristic: if you find\nyourself calling several setters together or your setter logic is branchy, a\nreducer makes intent clearer and easier to test.\n",{"id":98,"difficulty":33,"q":99,"a":100},"controlled-input","How do you build a controlled input with useState?","A controlled input gets its `value` from state and updates state on every\nkeystroke via `onChange`, making React the single source of truth.\n\n```jsx\nconst [text, setText] = useState('')\n\u003Cinput value={text} onChange={e => setText(e.target.value)} \u002F>\n```\n\nForgetting `onChange` while setting `value` makes the field **read-only** (React\nwarns). The benefit is you can validate, format, or react to every change; the\ncost is a re-render per keystroke (rarely a problem).\n",{"id":102,"difficulty":45,"q":103,"a":104},"controlled-vs-uncontrolled","What is the difference between controlled and uncontrolled components?","- **Controlled** — React state holds the value (`value` + `onChange`). Predictable\n  and easy to validate, but re-renders on each change.\n- **Uncontrolled** — the DOM holds the value; you read it via a `ref` when needed\n  (`defaultValue` for the initial value).\n\n```jsx\n\u002F\u002F uncontrolled\nconst ref = useRef()\n\u003Cinput defaultValue=\"hi\" ref={ref} \u002F>\n\u002F\u002F read ref.current.value on submit\n```\n\nControlled is the default recommendation; uncontrolled suits simple forms,\nfile inputs, or integrating non-React widgets.\n",{"id":106,"difficulty":45,"q":107,"a":108},"multiple-vs-single-object","Should you use multiple state variables or one state object?","Prefer **multiple `useState` calls** for values that change independently — it's\nsimpler and you don't have to spread-merge on every update. Group into one object\nonly when fields genuinely change **together**.\n\n```jsx\n\u002F\u002F independent values\nconst [name, setName] = useState('')\nconst [age, setAge] = useState(0)\n\n\u002F\u002F grouping requires manual merge (no auto-merge like class setState)\nsetForm(prev => ({ ...prev, name: 'Ada' }))\n```\n\nRemember the `useState` setter **replaces**, it doesn't merge — so an object of\nstate means spreading the rest every time.\n",{"id":110,"difficulty":62,"q":111,"a":112},"nested-object-update","How do you update deeply nested state immutably?","Spread at **every** level you change, all the way down — only the touched\nbranches get new references.\n\n```jsx\nsetUser(prev => ({\n  ...prev,\n  address: { ...prev.address, city: 'Paris' },\n}))\n```\n\nThis is error-prone and verbose for deep trees. Options: restructure to flatter\nstate, switch to `useReducer`, or use **Immer** (`produce`) which lets you\n\"mutate\" a draft and produces the immutable update for you.\n",{"id":114,"difficulty":45,"q":115,"a":116},"avoid-redundant-state","What is redundant state and why avoid it?","Redundant state is data you keep in `useState` that's already derivable from\nother state or props. It invites bugs because you must remember to update it\neverywhere, and the copies can disagree.\n\n```jsx\n\u002F\u002F fullName must be kept in sync manually\nconst [fullName, setFullName] = useState('')\n\u002F\u002F derive it\nconst fullName = `${first} ${last}`\n```\n\nKeep state **minimal and orthogonal** — the smallest set of independent values\nfrom which everything else is computed during render.\n",{"id":118,"difficulty":33,"q":119,"a":120},"toggle-boolean","How do you toggle a boolean in state?","Use the functional updater so you always flip the **latest** value, which matters\nif toggles can batch.\n\n```jsx\nconst [open, setOpen] = useState(false)\nconst toggle = () => setOpen(o => !o)\n```\n\nAvoid `setOpen(!open)` in code paths that may run multiple times in one event —\nit reads a possibly stale `open`. The functional form is always safe.\n",{"id":122,"difficulty":62,"q":123,"a":124},"prev-in-async","How do you read the latest state inside an async callback?","A callback captures the state value from the render it was created in, so after\nan `await` it may be **stale**. To act on the latest value, use the functional\nupdater (which receives the current value) or store it in a ref.\n\n```jsx\nasync function save() {\n  await delay(1000)\n  \u002F\u002F `count` is whatever it was when save() was created\n  \u002F\u002F read the latest via the updater\n  setCount(latest => { send(latest); return latest })\n}\n```\n\nCleaner still: pass the needed value as an argument, or keep a `useRef` mirror of\nthe state for \"read latest\" access without triggering renders.\n",{"id":126,"difficulty":62,"q":127,"a":128},"set-bailout","Does setting state to the same value cause a re-render?","If you set state to a value that's **`Object.is`-equal** to the current one,\nReact **bails out** and skips re-rendering that component (it may still re-run\nthe component once to check, then stop).\n\n```jsx\nconst [n, setN] = useState(0)\nsetN(0) \u002F\u002F same value -> React bails out, no committed re-render\n```\n\nThe catch: this is **reference** equality. `setItems([])` with a *new* empty\narray is a different reference, so it **does** re-render even though the contents\nlook identical. Don't create fresh objects\u002Farrays for \"no change.\"\n",{"id":130,"difficulty":33,"q":131,"a":132},"state-vs-props","What is the difference between state and props?","- **State** is data a component **owns and can change** over time via its setter.\n- **Props** are data **passed in from a parent**; the child treats them as\n  **read-only**.\n\n```jsx\nfunction Counter({ step }) {        \u002F\u002F step is a prop (read-only)\n  const [count, setCount] = useState(0) \u002F\u002F count is state (owned)\n  return \u003Cbutton onClick={() => setCount(c => c + step)}>{count}\u003C\u002Fbutton>\n}\n```\n\nA child never mutates props; to change parent data it calls a callback prop. One\ncomponent's state is often another's props (passed down).\n",{"id":134,"difficulty":45,"q":135,"a":136},"rules-of-hooks","Why must useState be called at the top level, not in conditions?","React identifies each hook by its **call order**, not a name. Calling `useState`\ninside a condition, loop, or after an early return changes that order between\nrenders, so React mismatches state to the wrong hook.\n\n```jsx\n\u002F\u002F breaks hook ordering\nif (loggedIn) {\n  const [name, setName] = useState('')\n}\n\u002F\u002F always call unconditionally; branch on the value\nconst [name, setName] = useState('')\nif (loggedIn) { \u002F* use name *\u002F }\n```\n\nAlways call hooks at the top level of the component, in the same order every\nrender — the `eslint-plugin-react-hooks` rule enforces this.\n",{"id":138,"difficulty":45,"q":139,"a":140},"state-no-rerender","When does updating state not trigger a re-render?","Two common cases: (1) you **mutated** the existing object\u002Farray and passed the\nsame reference, so React sees no change; or (2) you set a primitive to the\n**same value** (React bails out via `Object.is`).\n\n```jsx\n\u002F\u002F mutate + same reference -> no re-render\nuser.name = 'Ada'\nsetUser(user)\n\u002F\u002F new reference\nsetUser({ ...user, name: 'Ada' })\n```\n\nThe fix is always to produce a **new reference** for changed objects\u002Farrays. If\nthe UI \"isn't updating,\" this mutation trap is the first thing to check.\n",{"id":142,"difficulty":62,"q":143,"a":144},"set-during-render","Can you call a state setter during render?","Generally you should not — setting state in the render body unconditionally\ncauses an **infinite loop**. React *does* support a narrow pattern: calling a\nsetter **conditionally during render** to adjust state based on a prop change,\nwhich it handles without an extra paint.\n\n```jsx\n\u002F\u002F rare, allowed: derive on prop change without an effect\nconst [prevId, setPrevId] = useState(id)\nif (id !== prevId) {\n  setPrevId(id)\n  setSelection(null) \u002F\u002F reset when id changes, no effect needed\n}\n```\n\nMost of the time you don't need this — prefer deriving values or the `key` reset\ntrick. Never call a setter unconditionally in render.\n",{"id":146,"difficulty":45,"q":147,"a":148},"setstate-loop","What causes an infinite re-render loop with useState?","Calling a setter **unconditionally during render**, or inside an effect whose\ndependencies you keep changing, makes React render -> set -> render forever.\n\n```jsx\n\u002F\u002F sets state every render -> infinite loop\nconst [n, setN] = useState(0)\nsetN(n + 1)\n\n\u002F\u002F effect with a new object dep each render\nuseEffect(() => setData(load()), [{}])\n```\n\nFixes: only set state in **event handlers** or **effects with stable deps**,\nderive values instead of storing them, and avoid fresh object\u002Farray literals in\ndependency arrays.\n",{"id":150,"difficulty":62,"q":151,"a":152},"state-from-props","Why doesn't state update when the prop used to initialize it changes?","The `useState` initializer is only read on the **first render**. Passing a prop\nas the initial value captures it once; later changes to the prop don't flow into\nthe state.\n\n```jsx\nfunction Field({ initial }) {\n  const [value, setValue] = useState(initial) \u002F\u002F only the first `initial` is used\n  \u002F\u002F later `initial` changes are ignored\n}\n```\n\nIf you truly need to reset when the prop changes, use the **`key`** prop to\nremount, or the conditional set-during-render pattern. Often the better answer is\nto not copy the prop into state at all.\n",{"id":154,"difficulty":45,"q":155,"a":156},"expensive-derived","How do you cache an expensive derived value without storing it in state?","Use `useMemo` — it recomputes the value only when its dependencies change, so you\nget the benefit of a cached derivation **without** the desync risk of putting it\nin state.\n\n```jsx\nconst sorted = useMemo(\n  () => [...items].sort(compare),\n  [items]\n)\n```\n\nThis keeps `items` as the single source of truth while avoiding re-sorting on\nevery keystroke. Don't reach for `useMemo` until the computation is actually\nexpensive — needless memoization adds its own overhead.\n",{"id":158,"difficulty":45,"q":159,"a":160},"state-colocation","Where should state live in a React component tree?","Keep state as **close as possible** to where it's used (colocation), and lift it\nonly as high as the nearest common ancestor that needs to share it. Over-lifting\nstate to the top causes unnecessary re-renders and prop drilling.\n\n```jsx\n\u002F\u002F a modal's \"open\" state belongs in the component that owns the modal,\n\u002F\u002F not in the app root\nfunction Toolbar() {\n  const [open, setOpen] = useState(false)\n  \u002F\u002F ...\n}\n```\n\nColocated state means fewer renders and simpler components; global\u002Fapp state\nshould be reserved for genuinely shared data.\n",{"id":162,"difficulty":45,"q":163,"a":164},"form-generic-handler","How do you manage many form fields with one state object?","Hold the fields in one object and use a generic change handler keyed by the\ninput's `name`, spreading the previous object to preserve the other fields.\n\n```jsx\nconst [form, setForm] = useState({ name: '', email: '' })\nconst onChange = e =>\n  setForm(prev => ({ ...prev, [e.target.name]: e.target.value }))\n\n\u003Cinput name=\"email\" value={form.email} onChange={onChange} \u002F>\n```\n\nThe computed key `[e.target.name]` updates just one field. For large\u002Fvalidated\nforms, a form library or `useReducer` scales better than hand-rolling this.\n",{"id":166,"difficulty":45,"q":167,"a":168},"no-setstate-callback","Is there a setState callback like in class components?","No. The class `this.setState(value, callback)` second argument doesn't exist for\nthe `useState` setter. To run code **after** a state update commits, use a\n`useEffect` that depends on that state.\n\n```jsx\nconst [count, setCount] = useState(0)\nuseEffect(() => {\n  \u002F\u002F runs after the render caused by count changing\n  analytics.track(count)\n}, [count])\n```\n\nThis is the hooks way to \"do X after state changes\" — react to the new value in\nan effect rather than passing a callback to the setter.\n",{"id":170,"difficulty":33,"q":171,"a":172},"number-input","How do you handle number inputs with useState?","An `\u003Cinput>`'s value is always a **string**, so convert it when you need a\nnumber, and decide how to handle empty\u002Finvalid input.\n\n```jsx\nconst [age, setAge] = useState('')\n\u003Cinput\n  type=\"number\"\n  value={age}\n  onChange={e => setAge(e.target.value)} \u002F\u002F keep the raw string in state\n\u002F>\nconst ageNum = age === '' ? 0 : Number(age) \u002F\u002F parse where you use it\n```\n\nStoring the raw string avoids fighting the input (e.g. a partially typed `-` or\n`.`), and you parse to a number only at the point of use.\n",{"id":174,"difficulty":62,"q":175,"a":176},"batched-puzzle","What does calling the same setter three times with the value form produce?","```jsx\nconst [count, setCount] = useState(0)\nfunction handle() {\n  setCount(count + 1)\n  setCount(count + 1)\n  setCount(count + 1)\n} \u002F\u002F after one click, count is 1 — not 3\n```\n\nAll three read the **same** `count` (0) from this render's closure, each\ncomputing `1`, and batching collapses them to a single update of `1`. Use the\nfunctional form to actually add 3:\n\n```jsx\nsetCount(c => c + 1) \u002F\u002F ×3 -> each receives the previous result -> 3\n```\n\nThis puzzle tests whether you understand closures-over-state plus batching.\n",null,{"description":31},"React useState interview questions — state updates, batching, functional updates, lazy initialization and why state seems one render behind.","react\u002Fhooks\u002Fusestate","useState","2026-06-17","ecZdz3Hz9vchxC3tS5HAVYkPMt3Vp8u1wGhzVLUHQ4Q",{"id":185,"title":186,"body":187,"description":31,"difficulty":45,"extension":34,"framework":10,"frameworkSlug":8,"meta":191,"navigation":36,"order":11,"path":192,"questions":193,"related":177,"seo":334,"seoDescription":335,"stem":336,"subtopic":337,"topic":19,"topicSlug":20,"updated":182,"__hash__":338},"qa\u002Freact\u002Fhooks\u002Fuseeffect.md","Useeffect",{"type":28,"value":188,"toc":189},[],{"title":31,"searchDepth":11,"depth":11,"links":190},[],{},"\u002Freact\u002Fhooks\u002Fuseeffect",[194,198,202,206,210,214,218,222,226,230,234,238,242,246,250,254,258,262,266,270,274,278,282,286,290,294,298,302,306,310,314,318,322,326,330],{"id":195,"difficulty":33,"q":196,"a":197},"what-is-useeffect","What does useEffect do?","`useEffect` lets you run **side effects** — work that reaches outside React's\nrender output: fetching data, setting up subscriptions or timers, manually\ntouching the DOM, logging. Render must stay pure (no side effects), so React\ngives you `useEffect` as the escape hatch that runs **after** the component has\nrendered and the screen is updated.\n\n```jsx\nuseEffect(() => {\n  document.title = `${count} unread`\n}, [count])\n```\n\nThe mental model isn't \"run this on mount\u002Fupdate\" but \"**keep this external\nthing in sync** with the state and props in my dependency array.\" React runs\nthe effect whenever one of those inputs changes so the outside world matches\nthe latest render.\n",{"id":199,"difficulty":45,"q":200,"a":201},"deps-array","What does the dependency array control?","The dependency array tells React **when to re-run** the effect by comparing\neach item to its value from the previous render (using `Object.is`):\n\n- `[]` -> run **once** after the first render (no dependencies ever change).\n- `[a, b]` -> run after the first render, then again **only when `a` or `b`\n  change**.\n- **omitted** -> run after **every** render.\n\n```jsx\nuseEffect(() => {\n  const sub = source.subscribe(id)\n  return () => sub.unsubscribe()\n}, [id]) \u002F\u002F re-subscribe only when `id` changes\n```\n\nThe golden rule (enforced by the `react-hooks\u002Fexhaustive-deps` lint): every\nreactive value the effect *reads* — props, state, derived values — must be\nlisted. Omitting one to \"run less often\" is the #1 source of stale-data bugs.\n",{"id":203,"difficulty":45,"q":204,"a":205},"cleanup","What is the cleanup function and when does it run?","If your effect sets something up that needs tearing down, **return a function**\nfrom it — that's the cleanup. React runs it in two situations: **before\nre-running** the effect (to undo the previous run) and when the component\n**unmounts**. This prevents leaked listeners, duplicate subscriptions, and\n\"setState on unmounted component\" warnings.\n\n```jsx\nuseEffect(() => {\n  const id = setInterval(tick, 1000)\n  return () => clearInterval(id) \u002F\u002F tear down before next run \u002F on unmount\n}, [])\n```\n\nThe sequence on a dependency change is: run cleanup for the *old* deps -> run\nthe effect for the *new* deps. So with `[id]`, changing `id` from `1` to `2`\nunsubscribes from `1` first, then subscribes to `2`. Forgetting cleanup is how\nyou end up with N intervals firing after N renders.\n",{"id":207,"difficulty":33,"q":208,"a":209},"empty-deps","What happens with an empty dependency array?","An empty array `[]` means \"no reactive dependencies,\" so the effect runs\n**once** after the initial render and its cleanup runs **once** on unmount.\nIt's the closest hooks equivalent to the old `componentDidMount` +\n`componentWillUnmount` lifecycle pair.\n\n```jsx\nuseEffect(() => {\n  const onResize = () => setWidth(window.innerWidth)\n  window.addEventListener('resize', onResize)\n  return () => window.removeEventListener('resize', onResize)\n}, []) \u002F\u002F attach once, detach on unmount\n```\n\nCaveat for interviews: in React 18 **Strict Mode during development**, React\nintentionally mounts -> unmounts -> remounts components, so your `[]` effect and\nits cleanup fire twice. That's a deliberate check that your cleanup is correct;\nit does not happen in production.\n",{"id":211,"difficulty":62,"q":212,"a":213},"stale-closure","What is a stale closure in useEffect?","An effect closes over the props and state from the render it was created in.\nIf you read a value but **leave it out of the dependency array**, the effect\nkeeps using the *frozen* value from that render and never sees updates — a\nstale closure.\n\n```jsx\n\u002F\u002F count is captured once (as 0) and never updated -> logs 0, 0, 0...\nuseEffect(() => {\n  const id = setInterval(() => console.log(count), 1000)\n  return () => clearInterval(id)\n}, []) \u002F\u002F missing `count`\n\n\u002F\u002F Option A: list the dependency (re-creates the interval each change)\n\u002F\u002F Option B: use a functional updater so you don't read `count` at all\nsetCount(c => c + 1)\n```\n\nFixes: include every value you read in the deps, use the functional updater\nform so the stale value never matters, or stash the latest value in a `useRef`\nwhen you deliberately want a long-lived effect that reads fresh data.\n",{"id":215,"difficulty":62,"q":216,"a":217},"effect-timing","How does useEffect differ from useLayoutEffect?","Both run after render, but at different moments relative to the browser\n**paint**:\n\n- `useEffect` runs **asynchronously, after the browser has painted**. The user\n  sees the new frame, then the effect fires. Best for the vast majority of\n  effects (data, subscriptions) since it doesn't block visual updates.\n- `useLayoutEffect` runs **synchronously after DOM mutations but before\n  paint**. React blocks painting until it finishes, so you can measure layout\n  or mutate the DOM and the user never sees an intermediate state.\n\n```jsx\nuseLayoutEffect(() => {\n  const { height } = ref.current.getBoundingClientRect()\n  setTooltipTop(height) \u002F\u002F adjust position before the browser paints\n}, [])\n```\n\nUse `useLayoutEffect` only when you must read\u002Fwrite layout to avoid a visible\nflicker; because it's blocking, overusing it hurts performance. (On the server\nit doesn't run and warns — guard SSR code accordingly.)\n",{"id":219,"difficulty":45,"q":220,"a":221},"data-fetching","How do you fetch data inside useEffect?","Start the request in the effect and store the result in state. List every value\nthe request depends on (like an `id`) in the dependency array so it refetches\nwhen they change.\n\n```jsx\nuseEffect(() => {\n  let active = true\n  fetch(`\u002Fapi\u002Fuser\u002F${id}`)\n    .then(r => r.json())\n    .then(data => { if (active) setUser(data) })\n  return () => { active = false } \u002F\u002F ignore a stale response\n}, [id])\n```\n\nThe `active` flag (or an `AbortController`) prevents a slow earlier request from\noverwriting a newer one. In real apps a data library usually handles this better.\n",{"id":223,"difficulty":62,"q":224,"a":225},"fetch-race-condition","How do you avoid race conditions when fetching in an effect?","If `id` changes quickly, responses can arrive **out of order** and the older one\noverwrites the newer. Guard with a cleanup that invalidates the in-flight\nrequest — an ignore flag or an `AbortController`.\n\n```jsx\nuseEffect(() => {\n  const ctrl = new AbortController()\n  fetch(`\u002Fapi\u002F${id}`, { signal: ctrl.signal })\n    .then(r => r.json())\n    .then(setData)\n    .catch(e => { if (e.name !== 'AbortError') throw e })\n  return () => ctrl.abort() \u002F\u002F cancel the previous request\n}, [id])\n```\n\nReact runs the cleanup before the next effect, so the stale request is aborted\nand can't clobber fresh data.\n",{"id":227,"difficulty":45,"q":228,"a":229},"fetch-vs-library","Why prefer a data library over fetching in useEffect?","Hand-rolled `useEffect` fetching means you reimplement caching, deduping,\nretries, race-condition handling, loading\u002Ferror states, and refetching — for\nevery call. Libraries like **React Query \u002F SWR \u002F RTK Query** give you all of that\ndeclaratively.\n\n```jsx\nconst { data, isLoading, error } = useQuery({\n  queryKey: ['user', id],\n  queryFn: () => fetchUser(id),\n})\n```\n\nThe React docs explicitly recommend a framework or data library for fetching.\nUse raw effect-fetching only for simple, one-off cases.\n",{"id":231,"difficulty":62,"q":232,"a":233},"object-deps","Why does an object or array dependency re-run the effect every render?","Dependencies are compared by **reference** (`Object.is`). An object\u002Farray literal\ncreated during render is a **new reference each time**, so the effect sees a\n\"changed\" dependency on every render and re-runs endlessly.\n\n```jsx\n\u002F\u002F options is a new object every render -> effect runs every render\nconst options = { id }\nuseEffect(() => subscribe(options), [options])\n```\n\nFixes: depend on the **primitive** inside (`[id]`), build the object **inside**\nthe effect, or memoize it with `useMemo`. Same applies to functions passed as\ndeps — stabilize with `useCallback`.\n",{"id":235,"difficulty":62,"q":236,"a":237},"usecallback-dep","How does useCallback help with effect dependencies?","A function defined in a component body is recreated each render (new reference).\nIf an effect depends on it, the effect re-runs every render. `useCallback`\nreturns a **stable** function identity that only changes when its own deps do.\n\n```jsx\nconst load = useCallback(() => fetchData(id), [id])\nuseEffect(() => { load() }, [load]) \u002F\u002F re-runs only when id changes\n```\n\nWithout it, the effect would fire on every render. `useCallback` is mainly about\n**referential stability** for deps and memoized children — not raw speed.\n",{"id":239,"difficulty":45,"q":240,"a":241},"usememo-dep","How do you stabilize an object dependency with useMemo?","Wrap the object's creation in `useMemo` so it keeps the same reference until its\ninputs change, which keeps a dependent effect from re-running needlessly.\n\n```jsx\nconst filters = useMemo(() => ({ status, sort }), [status, sort])\nuseEffect(() => {\n  applyFilters(filters)\n}, [filters]) \u002F\u002F only when status or sort actually change\n```\n\nEquivalent alternative: skip the object and depend on `[status, sort]` directly.\nMemoize when the object must be passed around as a single value.\n",{"id":243,"difficulty":45,"q":244,"a":245},"polling-interval","How do you poll an API on an interval with useEffect?","Set up the interval in the effect and **clear it in cleanup** so it doesn't leak\nor duplicate when deps change or the component unmounts.\n\n```jsx\nuseEffect(() => {\n  const id = setInterval(() => refetch(), 5000)\n  return () => clearInterval(id)\n}, [refetch])\n```\n\nIf `refetch` isn't stable, wrap it in `useCallback` or you'll tear down and\nrecreate the interval each render. For variable intervals, a `useRef`-based\n`useInterval` hook is a common pattern.\n",{"id":247,"difficulty":62,"q":248,"a":249},"debounce-effect","How do you debounce a value with useEffect?","Start a timer in the effect that updates a \"debounced\" state, and **clear the\ntimer in cleanup** — so rapid changes keep resetting the timer until input\nsettles.\n\n```jsx\nconst [query, setQuery] = useState('')\nconst [debounced, setDebounced] = useState(query)\nuseEffect(() => {\n  const id = setTimeout(() => setDebounced(query), 300)\n  return () => clearTimeout(id) \u002F\u002F cancel if query changes again\n}, [query])\n\u002F\u002F run search on `debounced`, not `query`\n```\n\nEach keystroke re-runs the effect, whose cleanup cancels the previous pending\ntimer — so the update only fires after 300ms of quiet.\n",{"id":251,"difficulty":62,"q":252,"a":253},"subscribe-external","How do you subscribe to an external store from an effect?","Subscribe in the effect and **unsubscribe in cleanup**. For external stores,\nReact 18's `useSyncExternalStore` is the purpose-built hook (tear-free with\nconcurrent rendering), but a manual effect works for simple cases.\n\n```jsx\nuseEffect(() => {\n  const unsub = store.subscribe(() => setValue(store.get()))\n  setValue(store.get())   \u002F\u002F sync the initial value\n  return unsub            \u002F\u002F cleanup unsubscribes\n}, [store])\n```\n\nAlways read the current value once on subscribe so you don't miss a change that\nhappened between render and effect. Prefer `useSyncExternalStore` for shared\nstores.\n",{"id":255,"difficulty":45,"q":256,"a":257},"localstorage-sync","How do you sync state to localStorage with useEffect?","Write to `localStorage` in an effect that depends on the value, and read it lazily\nin the initializer so it's only parsed once.\n\n```jsx\nconst [theme, setTheme] = useState(() => localStorage.getItem('theme') ?? 'light')\nuseEffect(() => {\n  localStorage.setItem('theme', theme)\n}, [theme])\n```\n\nThe lazy initializer avoids reading storage on every render; the effect persists\nchanges. Guard `localStorage` access for SSR (it doesn't exist on the server) —\neffects don't run server-side, so this pattern is SSR-safe.\n",{"id":259,"difficulty":62,"q":260,"a":261},"not-need-effect","When do you NOT need a useEffect?","Effects are for **synchronizing with external systems**, not for reacting to user\nevents or computing values. If something happens **because the user did\nsomething**, do it in the **event handler**, not an effect.\n\n```jsx\n\u002F\u002F effect reacting to a click that already happened\nuseEffect(() => { if (submitted) postData() }, [submitted])\n\u002F\u002F just do it in the handler\nfunction onSubmit() { postData() }\n```\n\n\"You Might Not Need an Effect\" (React docs): skip effects for derived data,\nevent responses, and resetting state on prop change (use `key` instead).\n",{"id":263,"difficulty":45,"q":264,"a":265},"derive-not-effect","Why shouldn't you use an effect to compute derived state?","Storing computed data in state and syncing it with an effect causes an **extra\nrender** and risks the copy going stale. Just compute it during render.\n\n```jsx\n\u002F\u002F effect + extra state + extra render\nconst [full, setFull] = useState('')\nuseEffect(() => setFull(`${first} ${last}`), [first, last])\n\n\u002F\u002F derive in render (memoize only if expensive)\nconst full = `${first} ${last}`\n```\n\nAn effect-to-set-state for something derivable is a classic anti-pattern flagged\nin the React docs.\n",{"id":267,"difficulty":62,"q":268,"a":269},"lint-suppress-danger","Why is disabling the exhaustive-deps lint dangerous?","Adding `\u002F\u002F eslint-disable-next-line react-hooks\u002Fexhaustive-deps` to silence a\nmissing dependency doesn't fix the bug — it hides it. The effect keeps using\n**stale** captured values and silently breaks when those values change.\n\n```jsx\n\u002F\u002F suppressing the warning to \"run once\" -> stale `userId`\nuseEffect(() => { fetchData(userId) }, []) \u002F\u002F eslint-disable-line\n```\n\nInstead, fix the root cause: include the dep, move logic inside the effect, use a\nfunctional setter, or stabilize the value with `useCallback`\u002F`useRef`. Suppress\nonly when you fully understand why it's safe.\n",{"id":271,"difficulty":45,"q":272,"a":273},"cleanup-order2","In what order do cleanup and the next effect run?","On a dependency change, React runs the **previous** effect's cleanup **first**,\nthen runs the **new** effect. It never overlaps them.\n\n```jsx\nuseEffect(() => {\n  console.log('subscribe', id)\n  return () => console.log('unsubscribe', id)\n}, [id])\n\u002F\u002F id: 1 -> 2 logs: \"unsubscribe 1\" then \"subscribe 2\"\n```\n\nThis ordering guarantees you tear down the old subscription before setting up the\nnew one — no leaks, no duplicates. On unmount, only the last cleanup runs.\n",{"id":275,"difficulty":62,"q":276,"a":277},"strict-double","Why does my effect run twice in development?","In React 18 **Strict Mode** (development only), React mounts each component,\n**unmounts it, and remounts it** to surface effects that aren't cleanup-safe. So\na mount effect + its cleanup fire twice.\n\n```jsx\nuseEffect(() => {\n  console.log('run')        \u002F\u002F logs twice in dev Strict Mode\n  return () => console.log('cleanup')\n}, [])\n```\n\nIt does **not** happen in production. If double-invocation breaks something\n(e.g. duplicate requests), that's a sign your effect needs proper cleanup or\nshouldn't be an effect at all — Strict Mode is doing its job.\n",{"id":279,"difficulty":45,"q":280,"a":281},"infinite-effect-loop","What causes an infinite loop in useEffect?","Setting state in an effect whose dependency **changes as a result of that state**\n— or using an unstable object\u002Farray\u002Ffunction dependency created each render.\n\n```jsx\n\u002F\u002F sets data -> re-render -> new [] dep -> runs again -> loop\nuseEffect(() => setData(compute()), [{}])\n\n\u002F\u002F depends on the state it updates\nuseEffect(() => setCount(count + 1), [count])\n```\n\nFixes: stabilize deps (memoize objects\u002Ffunctions), depend on primitives, or use\na functional updater so the effect needn't depend on the state it sets.\n",{"id":283,"difficulty":45,"q":284,"a":285},"async-effect-directly","Why can't the useEffect callback be async?","An `async` function returns a **Promise**, but React expects the effect callback\nto return **either nothing or a cleanup function**. Returning a Promise breaks\ncleanup. Define an async function **inside** and call it.\n\n```jsx\n\u002F\u002F async effect returns a Promise, not a cleanup fn\nuseEffect(async () => { await load() }, [])\n\n\u002F\u002F inner async function\nuseEffect(() => {\n  (async () => { await load() })()\n}, [])\n```\n\nThis keeps the return value available for cleanup while still letting you use\n`await` inside.\n",{"id":287,"difficulty":33,"q":288,"a":289},"event-listener-effect","How do you add and remove a global event listener?","Add the listener in the effect and remove **the same function reference** in\ncleanup, so it's detached on unmount and not duplicated on re-runs.\n\n```jsx\nuseEffect(() => {\n  const onKey = e => { if (e.key === 'Escape') close() }\n  window.addEventListener('keydown', onKey)\n  return () => window.removeEventListener('keydown', onKey)\n}, [close])\n```\n\nThe cleanup must reference the **exact** function passed to `addEventListener`\n(an inline arrow in both calls won't match), which is why it's defined once\ninside the effect.\n",{"id":291,"difficulty":45,"q":292,"a":293},"multiple-effects","Should you split logic into multiple effects?","Yes — use **separate effects for unrelated concerns**, each with its own\ndependency array. One effect per responsibility is clearer and avoids re-running\nunrelated logic when only one dependency changes.\n\n```jsx\nuseEffect(() => { document.title = title }, [title])     \u002F\u002F title sync\nuseEffect(() => {\n  const id = connect(roomId)\n  return () => disconnect(id)\n}, [roomId])                                             \u002F\u002F connection\n```\n\nDon't cram title updates and a subscription into one effect just because they're\nin the same component — split by what they synchronize.\n",{"id":295,"difficulty":45,"q":296,"a":297},"effect-run-timing","When exactly does useEffect run relative to rendering?","The sequence is: React renders (calls your component) -> commits changes to the\nDOM -> **browser paints** -> *then* `useEffect` runs asynchronously. So effects\nnever block the visual update.\n\n```\nrender -> commit (DOM updated) -> paint -> useEffect\n```\n\nThis is why you shouldn't read final layout measurements that must be applied\n*before* paint in `useEffect` — use `useLayoutEffect` for those. For most work\n(fetching, subscriptions, logging), running after paint is exactly what you want.\n",{"id":299,"difficulty":62,"q":300,"a":301},"ref-latest-in-effect","How do you read the latest prop\u002Fstate in a long-lived effect without re-subscribing?","Mirror the value in a `useRef` updated each render, and read `ref.current` inside\nthe effect. The effect can keep an empty dep array (set up once) yet always see\nfresh data.\n\n```jsx\nconst cbRef = useRef(onTick)\nuseEffect(() => { cbRef.current = onTick }) \u002F\u002F keep it current\nuseEffect(() => {\n  const id = setInterval(() => cbRef.current(), 1000)\n  return () => clearInterval(id)\n}, []) \u002F\u002F interval created once, always calls the latest onTick\n```\n\nThis is the core of the `useInterval`\u002F`useEventCallback` patterns — avoid stale\nclosures without tearing down the subscription on every change.\n",{"id":303,"difficulty":33,"q":304,"a":305},"effect-once-mount","How do you run an effect only once when the component mounts?","Pass an **empty dependency array**. The effect runs after the first render and\nnever again (its cleanup runs on unmount).\n\n```jsx\nuseEffect(() => {\n  analytics.pageView()\n}, []) \u002F\u002F mount only\n```\n\nCaveat: in dev Strict Mode it runs twice, and the lint rule will warn if the\neffect actually *uses* props\u002Fstate you left out — so \"run once\" should genuinely\nhave no reactive dependencies.\n",{"id":307,"difficulty":45,"q":308,"a":309},"dep-primitive-vs-object","Why are primitive dependencies safer than object dependencies?","Primitives (`string`, `number`, `boolean`) compare by **value**, so equal values\nare \"unchanged\" and the effect doesn't re-run. Objects\u002Farrays\u002Ffunctions compare\nby **reference**, so a freshly created one looks changed every render.\n\n```jsx\nuseEffect(() => {}, [userId])        \u002F\u002F re-runs only when the number changes\nuseEffect(() => {}, [{ userId }])    \u002F\u002F new object each render -> always re-runs\n```\n\nPrefer depending on the **specific primitive fields** you read rather than a\nwhole object — it's both safer and more precise.\n",{"id":311,"difficulty":45,"q":312,"a":313},"server-no-run","Do effects run during server-side rendering?","No. `useEffect` (and `useLayoutEffect`) **do not run on the server** — they only\nrun in the browser after hydration. So effects are the right place for\nbrowser-only APIs (`window`, `localStorage`, `IntersectionObserver`).\n\n```jsx\nuseEffect(() => {\n  const mq = window.matchMedia('(min-width: 768px)') \u002F\u002F browser-only, safe here\n  \u002F\u002F ...\n}, [])\n```\n\nDon't access `window`\u002F`document` during render (it crashes SSR); defer it to an\neffect. `useLayoutEffect` additionally **warns** during SSR — guard or use\n`useEffect` there.\n",{"id":315,"difficulty":62,"q":316,"a":317},"reset-on-prop-change","How do you reset state when a prop changes, without an effect?","Avoid the effect-that-resets-state pattern. Prefer **changing the `key`** to\nremount the subtree, or the conditional set-during-render pattern for partial\nresets.\n\n```jsx\n\u002F\u002F remount on userId change -> all state resets\n\u003CProfile key={userId} userId={userId} \u002F>\n\n\u002F\u002F partial reset during render (no effect, no extra paint)\nconst [prevId, setPrevId] = useState(id)\nif (id !== prevId) { setPrevId(id); setComment('') }\n```\n\nUsing an effect to watch the prop and call setters causes an extra render and is\nflagged as an anti-pattern in the docs.\n",{"id":319,"difficulty":45,"q":320,"a":321},"cleanup-async-fetch","How does the ignore-flag pattern prevent setting state after unmount?","A boolean captured in the effect, flipped in cleanup, lets the async callback\ncheck whether the component is still mounted (and the request still current)\nbefore calling a setter.\n\n```jsx\nuseEffect(() => {\n  let ignore = false\n  load(id).then(data => { if (!ignore) setData(data) })\n  return () => { ignore = true } \u002F\u002F later resolution is ignored\n}, [id])\n```\n\nThis both prevents the \"can't update an unmounted component\" warning and avoids a\nstale earlier request overwriting newer data.\n",{"id":323,"difficulty":62,"q":324,"a":325},"chained-effects-antipattern","Why are chains of effects that trigger each other an anti-pattern?","An effect that sets state, which triggers another effect that sets more state,\ncreates cascading renders that are hard to follow and inefficient — each step is\na separate render pass.\n\n```jsx\n\u002F\u002F effect -> setState -> effect -> setState ...\nuseEffect(() => setB(a + 1), [a])\nuseEffect(() => setC(b * 2), [b])\n```\n\nPrefer computing the values **together during render** (derive `b` and `c` from\n`a`), or do the multi-step update in a single **event handler**. Reserve effects\nfor genuine external synchronization, not internal state cascades.\n",{"id":327,"difficulty":45,"q":328,"a":329},"effect-vs-render-purity","Why must side effects live in useEffect and not in the render body?","Rendering must be **pure** — given the same props\u002Fstate it returns the same JSX\nwith no side effects. React may call your component multiple times, bail out, or\ndiscard renders (concurrent features), so a side effect in render could run\nunexpectedly, repeatedly, or never.\n\n```jsx\n\u002F\u002F side effect during render — runs on every render, breaks purity\ndocument.title = title\n\u002F\u002F after commit, controlled by deps\nuseEffect(() => { document.title = title }, [title])\n```\n\nKeeping effects out of render is what lets React safely re-render and optimize.\n",{"id":331,"difficulty":33,"q":332,"a":333},"analytics-on-change","How do you run code after a specific value changes?","Use an effect that **depends on that value** — it runs after each commit where\nthe value changed.\n\n```jsx\nuseEffect(() => {\n  analytics.track('step_changed', { step })\n}, [step])\n```\n\nThis is the hooks replacement for the class `setState` callback or\n`componentDidUpdate` comparisons: react to the new value in an effect keyed on\nit. Make sure the dependency is the exact value you're reacting to.\n",{"description":31},"React useEffect interview questions and answers — the dependency array, cleanup functions, effect timing and common mistakes.","react\u002Fhooks\u002Fuseeffect","useEffect","WadYDpcEpirDvJ9N1brliOprDPb6eXUhfXGkfT5T7go",1781808674071]