State & Events
Manage local component state and handle user events with predictable update patterns.
useState Basics
useState returns a value and setter: `const [count, setCount] = useState(0)`. State updates trigger re-renders. The setter accepts a value or updater function — use the functional form when new state depends on previous state to avoid stale closures.
State is local to the component instance. Lifting state shares it between siblings via a common parent. Do not mutate state directly — always use the setter to ensure React detects changes.
- Setter triggers re-render
- Functional updates for derived state
- Never mutate state directly
const [count, setCount] = useState(0); setCount(prev => prev + 1);
Event Handling
React normalizes events with SyntheticEvent wrappers around native events. Pass functions to handlers: `onClick={handleClick}`, not `onClick={handleClick()}`. Inline arrows create new functions each render — acceptable for simple cases, useCallback when passing to memoized children.
Call `event.preventDefault()` and `event.stopPropagation()` when needed. For forms, prefer controlled inputs where React state is the source of truth for input values.
- Pass function references to handlers
- SyntheticEvent wraps native events
- Controlled inputs for forms
function handleSubmit(e: React.FormEvent) {
e.preventDefault();
save(formData);
}Batching and Concurrent Updates
React 18 batches state updates in event handlers, promises, and timeouts automatically — multiple setState calls in one synchronous block produce one re-render. Updates inside async gaps may batch depending on context.
useTransition marks updates as non-urgent, keeping the UI responsive during heavy renders. useDeferredValue defers updating a derived value. Use these for search filters and large list filtering without blocking input.
- Automatic batching in React 18
- useTransition for non-urgent updates
- useDeferredValue for deferred rendering
Derived State and Effects
Avoid storing values computable from props or other state — derive during render instead. Redundant state causes sync bugs when one source updates and the other does not.
When props change and you need to reset local state, use a key on the component to remount, or derive reset logic in useEffect with clear dependencies — not by copying props to state on every change.
- Derive during render, do not duplicate
- Key prop to reset component state
- Avoid syncing props to state blindly
State Structure
Group related state into objects when updates happen together: `const [form, setForm] = useState({ name: '', email: '' })`. Split unrelated state into separate useState calls so updates do not unnecessarily rerender unrelated logic.
For complex state with interdependent transitions, upgrade to useReducer. For global shared state, consider Context or dedicated state libraries — do not lift everything to the root prematurely.
- Colocate related state
- Split unrelated state for render efficiency
- useReducer for complex transitions