React Hooks come with two hard rules that govern where and how they can be called. First, hooks must always be invoked at the top level of a React function — inside a component or another hook — never inside loops, conditions, or try/catch blocks. The reason is that React reconciles hook state by call order: each render must call the same hooks in the same order, or the linter will refuse to compile and React will throw at runtime. Second, hooks can only be called from React functions: components, custom hooks, or other hooks. Calling them from plain utilities or module-level code has no component instance to attach state to, which is why libraries expose them with the use prefix. The react-hooks/exhaustive-deps ESLint rule and React DevTools both rely on this naming convention to recognize hooks and inspect their state. Conditional hooks like if (x) useState(0) are the canonical violation: the hook count changes between renders, the call order drifts, and React can no longer associate state with the right slot.
StrictMode is the other big behavioral constraint to internalize. In development, React intentionally mounts every component, runs its effects, unmounts it, and remounts it again. This double-invocation surfaces effects that miss cleanups: a socket left open, a timer left ticking, a subscription left dangling. Production builds skip this rehearsal, so the same code behaves differently between dev and prod, but the discipline you build under StrictMode is what makes your effects safe in production. You should never write logic that assumes a single execution. The same principle explains why render itself runs twice in StrictMode: React wants to surface any non-pure side effect that mutates state during render.
Another foundational trap is calling setState directly inside the render body. Doing so schedules another render, which re-runs the function, which calls setState again — an infinite loop. The exception is the "derived state from props" pattern, where you setState only if a prop actually changed, but React's official guidance is to compute derived values during render or to reset state via a key prop instead. Related mistakes include setting state on another component from inside render (defer to useEffect), and conditionally rendering numbers in JSX with {count && <X/>}, which renders the literal 0 when count is zero. Coerce with !!count or write {count > 0 ? <X/> : null}. Throughout all of this, the underlying theme is the same: hooks give you a controlled way to express state and effects, and they only stay controlled if you respect their invariants.