skip to content

React 19's `use` may be called inside an `if` block or a loop, while `useState` may not. What about `use` makes that legal, and which placement rules still apply to it?

level: middleimportance: must knowfreq 58%

answer

  1. hooks are matched by call order
  2. slots on the fiber, walked by a cursor
  3. use() stores nothing per call site
  4. reading is safe; allocating is not
  5. still component-or-Hook, still no try/catch

basics

~20 s

use() claims no per-component state slot — it just reads an already-existing resource — so skipping or repeating the call cannot misalign anything. It must still be called inside a component or custom Hook, and never inside try/catch.

solid answer

~40 s

Hooks like `useState` are matched to their stored state by call order: React keeps an ordered list of slots per component, and the first `useState` call gets slot one on every render. A conditional call would shift every later hook onto the wrong slot, which is why they must be at the top level. `use` stores nothing — it reads a resource that already exists, a promise or a context value — so calling it once, twice, or not at all in a given render misaligns nothing, and React 19 explicitly permits it inside conditionals and loops. The rules that do survive: `use` must be called from a component or a custom Hook during render, not from an event handler or a plain utility function, and it cannot be wrapped in a `try`/`catch` block.

code

jsx · 12 lines
jsx
import { use } from 'react';

function Message({ show, messagePromise }) {
  if (show) {
    // Legal in React 19: use() reserves no hook slot.
    const message = use(messagePromise);
    return <p>{message}</p>;
  }
  return <p>Hidden</p>;
}

export default Message;

go deeper

for a junior

Remember the headline: use() is the one React 19 API you may call inside an if or a loop, while useState and useEffect must stay at the top level of the component.

for a middle

Explain the slot mechanism — React matches hooks to stored state by call order — and say that use() reserves no slot, so a skipped or repeated call misaligns nothing.

for a senior

Be ready to defend the remaining constraints in review: use() belongs in render inside a component or custom Hook, never in a handler, and never inside try/catch where it would swallow React's control-flow throw.

for a principal

Frame the exemption as an API-design signal: readers can be conditional, allocators cannot. Use that line when judging whether a proposed team abstraction is safe to call from a branch.

## Why ordinary hooks are order-dependent A function component has no `this`, so React needs some other way to associate `useState(0)` on this render with the value it stored on the previous render. It uses position. Each component's fiber holds an ordered list of hook records, and React walks that list with a cursor as the component renders: the first hook call reads record one, the second reads record two, and so on. ```jsx function Panel({ open }) { if (open) { const [width, setWidth] = useState(0); // ILLEGAL } const [height, setHeight] = useState(0); } ``` On a render where `open` is true there are two calls; where it is false there is one, and `height` now reads the record that belonged to `width`. State appears to leak between variables, effects re-fire, and React usually detects the count change and errors instead. Hence the top-level rule: the same hooks, in the same order, every render. ## Why use() is exempt `use` does not allocate a record. It is a *reader*: - `use(promise)` inspects a promise's status. Pending means suspend the component; fulfilled means return the value; rejected means throw. All of that state lives on the promise itself, not in a slot React owns for this call site. - `use(SomeContext)` walks up the fiber tree for the nearest matching provider and returns its value. Again, nothing is stored per call site. Because there is no cursor to keep aligned, calling `use` zero times on one render and three times on the next is harmless. React 19 therefore documents it as callable inside conditionals and loops — the one hook-shaped API with that freedom, which is a large part of why it is named `use` rather than `useSomething`. ```jsx function Message({ show, messagePromise }) { if (show) { const message = use(messagePromise); // legal in React 19 return <p>{message}</p>; } return <p>Hidden</p>; } ``` The practical payoff is real: you can skip work entirely on branches that do not need it, instead of unconditionally reading a resource and discarding the result. ## The rules that remain **Component or Hook only.** `use` still has to be called from a React component or a custom Hook while it renders. It is not callable from an event handler, a `setTimeout` callback, a module top level, or a plain helper function, because outside render there is no fiber to suspend and no tree to walk for context. **No try/catch.** React does not support wrapping a `use` call in `try`/`catch`. Suspension and rejection are both delivered by throwing out of the component, and a local `catch` would intercept React's own control-flow signal. To handle a rejection you rely on an error boundary above the component, or you attach `.catch()` to the promise before it ever reaches `use` so it fulfills with a fallback value instead of rejecting. **Render-time semantics still apply.** Because the call happens during render, it participates in the render's outcome: a pending promise suspends the whole component, no matter how deep in a branch the call sits. ## What this means for the lint rules The React hooks lint rules know about the exemption, so a conditional `use` is not reported the way a conditional `useState` is. Do not generalise from that: nothing else in the React API gains conditional-call rights, and "I saw a hook called inside an `if`" is not evidence that the rule has relaxed. ## How to say it in one breath Hooks are positional because they own state; `use` owns no state, so position does not matter. Everything else about where React APIs may be called still holds.

  • If use() can be called conditionally, why is it not simply allowed anywhere, like a normal function?
    Because its effects are render-scoped. A pending promise must suspend a component, and a context read must resolve against the fiber currently being rendered. Outside a component or custom Hook render there is no such fiber, so React has nothing to suspend and no provider chain to walk.
  • What breaks if you wrap a use() call in try/catch?
    React delivers both suspension and rejection by throwing out of the component, so a local catch swallows a signal React needs. React does not support it. Handle failure with an error boundary above the component, or attach .catch() to the promise before passing it in so it fulfills with a fallback value.
  • Does calling use() inside a loop create a hook-count mismatch across renders?
    No. Because use() allocates no hook record, the number of calls can differ between renders without shifting anything. The mismatch error exists only for hooks that reserve a slot, such as useState, useReducer, useRef and useEffect.

saying these in an interview costs you the question

  • Says the top-level rule was relaxed for all hooks in React 19
  • Claims a compiler rewrites conditional hook calls to make them safe
  • Thinks use() can be called from event handlers or plain functions
  • Believes try/catch around use() is the normal error-handling path
  • Cannot explain that hooks are matched to state by call order

context