logo by @sawaratsuki1004
React
v19.2
Learn
Reference
Community
Blog

Is this page useful?

On this page

  • Overview
  • Rule Details
  • Common Violations
  • Invalid
  • Valid
  • Troubleshooting
  • I want to sync state to a prop

    react@19.2

  • Overview
  • Hooks
    • useActionState
    • useCallback
    • useContext
    • useDebugValue
    • useDeferredValue
    • useEffect
    • useEffectEvent
    • useId
    • useImperativeHandle
    • useInsertionEffect
    • useLayoutEffect
    • useMemo
    • useOptimistic
    • useReducer
    • useRef
    • useState
    • useSyncExternalStore
    • useTransition
  • المكونات
    • <Fragment> (<>)
    • <Profiler>
    • <StrictMode>
    • <Suspense>
    • <Activity>
    • <ViewTransition> - This feature is available in the latest Canary version of React
  • APIs
    • act
    • addTransitionType - This feature is available in the latest Canary version of React
    • cache
    • cacheSignal
    • captureOwnerStack
    • createContext
    • lazy
    • memo
    • startTransition
    • use
    • experimental_taintObjectReference - This feature is available in the latest Experimental version of React
    • experimental_taintUniqueValue - This feature is available in the latest Experimental version of React
  • react-dom@19.2

  • Hooks
    • useFormStatus
  • المكونات
    • Common (e.g. <div>)
    • <form>
    • <input>
    • <option>
    • <progress>
    • <select>
    • <textarea>
    • <link>
    • <meta>
    • <script>
    • <style>
    • <title>
  • APIs
    • createPortal
    • flushSync
    • preconnect
    • prefetchDNS
    • preinit
    • preinitModule
    • preload
    • preloadModule
  • Client APIs
    • createRoot
    • hydrateRoot
  • Server APIs
    • renderToPipeableStream
    • renderToReadableStream
    • renderToStaticMarkup
    • renderToString
    • resume
    • resumeToPipeableStream
  • Static APIs
    • prerender
    • prerenderToNodeStream
    • resumeAndPrerender
    • resumeAndPrerenderToNodeStream
  • React Compiler

  • Configuration
    • compilationMode
    • gating
    • logger
    • panicThreshold
    • target
  • Directives
    • "use memo"
    • "use no memo"
  • Compiling Libraries
  • React DevTools

  • React Performance tracks
  • eslint-plugin-react-hooks

  • Lints
    • exhaustive-deps
    • rules-of-hooks
    • component-hook-factories
    • config
    • error-boundaries
    • gating
    • globals
    • immutability
    • incompatible-library
    • preserve-manual-memoization
    • purity
    • refs
    • set-state-in-effect
    • set-state-in-render
    • static-components
    • unsupported-syntax
    • use-memo
  • Rules of React

  • Overview
    • Components and Hooks must be pure
    • React calls Components and Hooks
    • Rules of Hooks
  • React Server Components

  • Server Components
  • Server Functions
  • Directives
    • 'use client'
    • 'use server'
  • Legacy APIs

  • Legacy React APIs
    • Children
    • cloneElement
    • Component
    • createElement
    • createRef
    • forwardRef
    • isValidElement
    • PureComponent
API Reference
Lints

set-state-in-render

Validates against unconditionally setting state during render, which can trigger additional renders and potential infinite render loops.

Rule Details

Calling setState during render unconditionally triggers another render before the current one finishes. This creates an infinite loop that crashes your app.

Common Violations

Invalid

// ❌ Unconditional setState directly in render function Component({value}) { const [count, setCount] = useState(0); setCount(value); // Infinite loop! return <div>{count}</div>; }

Valid

// ✅ Derive during render function Component({items}) { const sorted = [...items].sort(); // Just calculate it in render return <ul>{sorted.map(/*...*/)}</ul>; } // ✅ Set state in event handler function Component() { const [count, setCount] = useState(0); return ( <button onClick={() => setCount(count + 1)}> {count} </button> ); } // ✅ Derive from props instead of setting state function Component({user}) { const name = user?.name || ''; const email = user?.email || ''; return <div>{name}</div>; } // ✅ Conditionally derive state from props and state from previous renders function Component({ items }) { const [isReverse, setIsReverse] = useState(false); const [selection, setSelection] = useState(null); const [prevItems, setPrevItems] = useState(items); if (items !== prevItems) { // This condition makes it valid setPrevItems(items); setSelection(null); } // ... }

Troubleshooting

I want to sync state to a prop

A common problem is trying to “fix” state after it renders. Suppose you want to keep a counter from exceeding a max prop:

// ❌ Wrong: clamps during render function Counter({max}) { const [count, setCount] = useState(0); if (count > max) { setCount(max); } return ( <button onClick={() => setCount(count + 1)}> {count} </button> ); }

As soon as count exceeds max, an infinite loop is triggered.

Instead, it’s often better to move this logic to the event (the place where the state is first set). For example, you can enforce the maximum at the moment you update state:

// ✅ Clamp when updating function Counter({max}) { const [count, setCount] = useState(0); const increment = () => { setCount(current => Math.min(current + 1, max)); }; return <button onClick={increment}>{count}</button>; }

Now the setter only runs in response to the click, React finishes the render normally, and count never crosses max.

In rare cases, you may need to adjust state based on information from previous renders. For those, follow this pattern of setting state conditionally.

Previousset-state-in-effect
Nextstatic-components

Copyright © Meta Platforms, Inc
no uwu plz
uwu?
Logo by@sawaratsuki1004
Learn React
Quick Start
Installation
Describing the UI
Adding Interactivity
Managing State
Escape Hatches
API Reference
React APIs
React DOM APIs
Community
Code of Conduct
Meet the Team
Docs Contributors
Acknowledgements
More
Blog
React Native
Privacy
Terms
// ❌ Unconditional setState directly in render
function Component({value}) {
const [count, setCount] = useState(0);
setCount(value); // Infinite loop!
return <div>{count}</div>;
}
// ✅ Derive during render
function Component({items}) {
const sorted = [...items].sort(); // Just calculate it in render
return <ul>{sorted.map(/*...*/)}</ul>;
}

// ✅ Set state in event handler
function Component() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
);
}

// ✅ Derive from props instead of setting state
function Component({user}) {
const name = user?.name || '';
const email = user?.email || '';
return <div>{name}</div>;
}

// ✅ Conditionally derive state from props and state from previous renders
function Component({ items }) {
const [isReverse, setIsReverse] = useState(false);
const [selection, setSelection] = useState(null);

const [prevItems, setPrevItems] = useState(items);
if (items !== prevItems) { // This condition makes it valid
setPrevItems(items);
setSelection(null);
}
// ...
}
// ❌ Wrong: clamps during render
function Counter({max}) {
const [count, setCount] = useState(0);

if (count > max) {
setCount(max);
}

return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
);
}
// ✅ Clamp when updating
function Counter({max}) {
const [count, setCount] = useState(0);

const increment = () => {
setCount(current => Math.min(current + 1, max));
};

return <button onClick={increment}>{count}</button>;
}