React 19 New Hooks
HardReact 19 introduces four new hooks that dramatically simplify form handling and async data loading. useActionState manages form submission state and pending status. useFormStatus reads the parent form's submission status without prop drilling. useOptimistic enables instant UI updates that revert on failure. The use() hook reads promises and context conditionally during render.
Interactive Visualization
Key Points
- useActionState replaces manual useState + isPending + try/catch for form submissions
- useFormStatus reads parent form submission status without prop drilling — the component must be a child of a <form>
- useOptimistic provides instant UI feedback that auto-reverts when the async action settles
- use() reads promises (suspending until resolved) and context values conditionally — not bound by Rules of Hooks
- All four hooks integrate with Server Actions for progressive enhancement that works before JavaScript loads
Code Examples
useActionState for Form Handling
import { useActionState } from 'react' interface FormState { error: string | null success: boolean } async function submitForm( prevState: FormState, formData: FormData ): Promise<FormState> { const email = formData.get('email') as string try { await subscribe(email) return { error: null, success: true } } catch (e) { return { error: (e as Error).message, success: false } } } function NewsletterForm() { const [state, action, isPending] = useActionState( submitForm, { error: null, success: false } ) return ( <form action={action}> <input name="email" type="email" required /> {state.error && <p className="error">{state.error}</p>} <button disabled={isPending}> {isPending ? 'Subscribing...' : 'Subscribe'} </button> </form> ) }
useActionState takes an async action and initial state, returning [state, boundAction, isPending]. The action receives previous state and FormData, returns next state. React manages the pending flag automatically.
useOptimistic for Instant Feedback
import { useOptimistic, useActionState } from 'react' interface Todo { id: string text: string done: boolean pending?: boolean } function TodoList({ todos }: { todos: Todo[] }) { const [optimisticTodos, addOptimistic] = useOptimistic( todos, (state: Todo[], newTodo: string) => [ ...state, { id: 'temp', text: newTodo, done: false, pending: true }, ] ) async function addAction( prev: { error: string | null }, formData: FormData ): Promise<{ error: string | null }> { const text = formData.get('text') as string addOptimistic(text) await createTodo(text) return { error: null } } const [, action] = useActionState(addAction, { error: null }) return ( <div> {optimisticTodos.map(todo => ( <div key={todo.id} style={{ opacity: todo.pending ? 0.5 : 1 }}> {todo.text} </div> ))} <form action={action}> <input name="text" /> <button>Add</button> </form> </div> ) }
useOptimistic shows the new todo immediately with a pending visual indicator. When the server confirms, the real todo replaces the optimistic one. If the action fails, the optimistic entry disappears automatically.
use() for Conditional Context and Promises
import { use, Suspense, createContext } from 'react' const ThemeContext = createContext<'light' | 'dark'>('dark') // use() can be called conditionally (unlike useContext) function Greeting({ showTheme }: { showTheme: boolean }) { if (showTheme) { const theme = use(ThemeContext) return <h1 className={theme}>Themed greeting!</h1> } return <h1>Plain greeting</h1> } // use() can read promises (suspends until resolved) function UserProfile({ userPromise, }: { userPromise: Promise<{ name: string }> }) { const user = use(userPromise) return <h2>{user.name}</h2> } function Page() { const userPromise = fetchUser('1') return ( <Suspense fallback={<div>Loading...</div>}> <UserProfile userPromise={userPromise} /> </Suspense> ) }
use() is unique: it reads promises (integrating with Suspense) and context values, but unlike other hooks, it can be called conditionally. When reading a promise, the component suspends until the value resolves.
Common Mistakes
- Calling useFormStatus outside a <form> element — it only reads status from the nearest parent form
- Creating a new Promise on every render when using use() — the promise must be stable (created outside the component or memoized)
- Forgetting that useOptimistic reverts based on the source prop, not the action result — the source of truth must update for the optimistic value to settle
Interview Tips
- Explain how useActionState eliminates the useState + isPending + try/catch boilerplate pattern
- Know that use() breaks the Rules of Hooks intentionally — it can be conditional because it uses a different internal mechanism
- Discuss progressive enhancement: Server Actions + useActionState make forms work before JavaScript loads
Related Concepts
useState & State Updates
Adding and updating local component state
useEffect & Side Effects
Synchronizing components with external systems
Custom Hooks
Reusable stateful logic extracted into functions
Server Actions
Server-side form handling without API routes
React Compiler
Automatic memoization that replaces useMemo and useCallback