Carefully manage dependencies in React hooks to avoid stale closures and ensure correct behavior. When using useCallback, useMemo, or useEffect, include all reactive values that the hook depends on in the dependency array, even if it affects stability.

Common issues to watch for:

Example of problematic stale closure:

const matchIndex = useMatch({ select: (match) => match.index })

const navigate = React.useCallback((options) => {
  // matchIndex here is stale - captured from first render
  const from = router.state.matches[matchIndex]?.fullPath
  return router.navigate({ from, ...options })
}, [router]) // matchIndex missing from deps

// Better: obtain fresh values inside the callback
const navigate = React.useCallback((options) => {
  const currentMatches = router.state.matches
  const from = currentMatches[currentMatches.length - 1]?.fullPath
  return router.navigate({ from, ...options })
}, [router])

For custom hooks with async operations, avoid useState for promises that can become stale in development mode. Instead, create promises only when needed:

// Problematic: promise created on every render
const [promise, setPromise] = useState(createPromise)

// Better: create promise only when blocking occurs
const blockerFn = async () => {
  const promise = new Promise((resolve) => {
    setResolver({ proceed: () => resolve(true) })
  })
  return await promise
}

Always prioritize correctness over performance optimizations like callback stability.