Deft
Ask AI

Search

Search skills, authors and topics

vercel-labs / agent-skills

vercel-react-native-skills

React Native and Expo best practices for building performant mobile apps. Use when building React Native components, optimizing list performance, implementing animations, or working with native modules. Triggers on tasks involving React Native, Expo, mobile performance, or native platform APIs.

Install

npx skills add https://github.com/vercel-labs/agent-skills --skill vercel-react-native-skills
title
useState Dispatch updaters for State That Depends on Current Value
impact
MEDIUM
impactDescription
avoids stale closures, prevents unnecessary re-renders
tags
state, hooks, useState, callbacks

Use Dispatch Updaters for State That Depends on Current Value

When the next state depends on the current state, use a dispatch updater (setState(prev => ...)) instead of reading the state variable directly in a callback. This avoids stale closures and ensures you're comparing against the latest value.

Incorrect (reads state directly):

tsx
const [size, setSize] = useState<Size | undefined>(undefined)
const onLayout = (e: LayoutChangeEvent) => {  const { width, height } = e.nativeEvent.layout  // size may be stale in this closure  if (size?.width !== width || size?.height !== height) {    setSize({ width, height })  }}

Correct (dispatch updater):

tsx
const [size, setSize] = useState<Size | undefined>(undefined)
const onLayout = (e: LayoutChangeEvent) => {  const { width, height } = e.nativeEvent.layout  setSize((prev) => {    if (prev?.width === width && prev?.height === height) return prev    return { width, height }  })}

Returning the previous value from the updater skips the re-render.

For primitive states, you don't need to compare values before firing a re-render.

Incorrect (unnecessary comparison for primitive state):

tsx
const [size, setSize] = useState<Size | undefined>(undefined)
const onLayout = (e: LayoutChangeEvent) => {  const { width, height } = e.nativeEvent.layout  setSize((prev) => (prev === width ? prev : width))}

Correct (sets primitive state directly):

tsx
const [size, setSize] = useState<Size | undefined>(undefined)
const onLayout = (e: LayoutChangeEvent) => {  const { width, height } = e.nativeEvent.layout  setSize(width)}

However, if the next state depends on the current state, you should still use a dispatch updater.

Incorrect (reads state directly from the callback):

tsx
const [count, setCount] = useState(0)
const onTap = () => {  setCount(count + 1)}

Correct (dispatch updater):

tsx
const [count, setCount] = useState(0)
const onTap = () => {  setCount((prev) => prev + 1)}