Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Build performant React Native and Expo apps with best practices for lists, animations, and navigation
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
rules/react-state-dispatcher.md
1---2title: useState Dispatch updaters for State That Depends on Current Value3impact: MEDIUM4impactDescription: avoids stale closures, prevents unnecessary re-renders5tags: state, hooks, useState, callbacks6---78## Use Dispatch Updaters for State That Depends on Current Value910When the next state depends on the current state, use a dispatch updater11(`setState(prev => ...)`) instead of reading the state variable directly in a12callback. This avoids stale closures and ensures you're comparing against the13latest value.1415**Incorrect (reads state directly):**1617```tsx18const [size, setSize] = useState<Size | undefined>(undefined)1920const onLayout = (e: LayoutChangeEvent) => {21const { width, height } = e.nativeEvent.layout22// size may be stale in this closure23if (size?.width !== width || size?.height !== height) {24setSize({ width, height })25}26}27```2829**Correct (dispatch updater):**3031```tsx32const [size, setSize] = useState<Size | undefined>(undefined)3334const onLayout = (e: LayoutChangeEvent) => {35const { width, height } = e.nativeEvent.layout36setSize((prev) => {37if (prev?.width === width && prev?.height === height) return prev38return { width, height }39})40}41```4243Returning the previous value from the updater skips the re-render.4445For primitive states, you don't need to compare values before firing a46re-render.4748**Incorrect (unnecessary comparison for primitive state):**4950```tsx51const [size, setSize] = useState<Size | undefined>(undefined)5253const onLayout = (e: LayoutChangeEvent) => {54const { width, height } = e.nativeEvent.layout55setSize((prev) => (prev === width ? prev : width))56}57```5859**Correct (sets primitive state directly):**6061```tsx62const [size, setSize] = useState<Size | undefined>(undefined)6364const onLayout = (e: LayoutChangeEvent) => {65const { width, height } = e.nativeEvent.layout66setSize(width)67}68```6970However, if the next state depends on the current state, you should still use a71dispatch updater.7273**Incorrect (reads state directly from the callback):**7475```tsx76const [count, setCount] = useState(0)7778const onTap = () => {79setCount(count + 1)80}81```8283**Correct (dispatch updater):**8485```tsx86const [count, setCount] = useState(0)8788const onTap = () => {89setCount((prev) => prev + 1)90}91```92