InterviewsVector

React 19 Features for Interviews: Actions, use(), the Compiler, useOptimistic & ref-as-prop

Quick answer

React 19 adds Actions (async transitions wired into <form action={fn}> with useActionState and useFormStatus for pending/error state), the use() hook to read a promise or context (and unlike useContext it can be called conditionally), the React Compiler that auto-memoizes so you write far fewer useMemo/useCallback calls, useOptimistic for optimistic UI, ref passed as a normal prop (no forwardRef), and native <title>/<meta> hoisting from components.

Short answer: React 19 adds Actions (async work wired into <form action={fn}> with useActionState/useFormStatus), the use() hook (read a promise or context, callable conditionally), the React Compiler (automatic memoization — far fewer useMemo/useCallback), useOptimistic, ref as a normal prop (no forwardRef), and native <title>/<meta> hoisting.

"What's new in React 19?" is now a standard frontend interview question. Naming features isn't enough — interviewers want the correct usage and the why. Here's the set that comes up, with code that actually runs.

Actions: async work wired into forms

Before React 19 you wrote onSubmit, called preventDefault, and hand-managed useState for pending and error. React 19 wires an async function straight into the form and manages that state for you:

import { useActionState } from "react"
 
function UpdateName() {
  const [error, submitAction, pending] = useActionState(
    async (_prev: string | null, formData: FormData) => {
      const name = String(formData.get("name") ?? "")
      const err = await updateName(name) // returns an error message or null
      return err ?? null
    },
    null
  )
 
  return (
    <form action={submitAction}>
      <input name="name" />
      <button disabled={pending}>Update</button>
      {error && <p role="alert">{error}</p>}
    </form>
  )
}

Nested components can read the submitting state without prop drilling via useFormStatus:

import { useFormStatus } from "react-dom"
 
function SubmitButton() {
  const { pending } = useFormStatus() // reads the enclosing <form>'s status
  return <button disabled={pending}>{pending ? "Saving…" : "Save"}</button>
}

useOptimistic: instant feedback

Show the expected result immediately, then reconcile when the real action resolves:

import { useOptimistic } from "react"
 
function Thread({ messages, sendMessage }) {
  const [optimistic, addOptimistic] = useOptimistic(
    messages,
    (state, text: string) => [...state, { text, sending: true }]
  )
 
  async function action(formData: FormData) {
    const text = String(formData.get("text"))
    addOptimistic(text)          // appears instantly
    await sendMessage(text)      // real send; list re-syncs on resolve
  }
 
  return (
    <>
      {optimistic.map((m, i) => (
        <p key={i}>{m.text}{m.sending ? " (sending…)" : ""}</p>
      ))}
      <form action={action}><input name="text" /></form>
    </>
  )
}

The use() hook

use() reads a resource. With a promise it suspends (pair with <Suspense>); with a context it returns the value — and unlike useContext, it can be called conditionally:

import { use } from "react"
 
function Comments({ commentsPromise }: { commentsPromise: Promise<Comment[]> }) {
  const comments = use(commentsPromise) // suspends until resolved
  return <ul>{comments.map((c) => <li key={c.id}>{c.text}</li>)}</ul>
}
 
function Item({ show, ThemeContext }) {
  if (!show) return null
  const theme = use(ThemeContext) // ✅ conditional — illegal with useContext
  return <div className={theme}>…</div>
}

The React Compiler: automatic memoization

The compiler analyses your components and memoizes automatically, so the ritual of wrapping everything in useMemo/useCallback/React.memo largely goes away:

// React 19 + compiler: no manual memoization needed for this to be efficient
function ProductList({ products, query }) {
  const filtered = products.filter((p) => p.name.includes(query))
  return <List items={filtered} />
}

In an interview, the nuance to state: the compiler doesn't change semantics, it removes the manual memoization burden — you reach for useMemo only where the compiler can't prove safety or isn't enabled.

ref as a prop — no more forwardRef

// React 19: ref is just a prop
function TextInput({ ref, ...props }: { ref?: React.Ref<HTMLInputElement> }) {
  return <input ref={ref} {...props} />
}

Native document metadata

<title> and <meta> rendered anywhere in the tree are hoisted into <head>:

function BlogPost({ post }) {
  return (
    <article>
      <title>{post.title}</title>
      <meta name="description" content={post.excerpt} />
      {/* body */}
    </article>
  )
}

Common interview traps

  • Confusing Actions with onSubmit — Actions use <form action={fn}>; the old pattern is onSubmit + preventDefault.
  • Calling use() like await — it suspends the component; wrap it in <Suspense>, don't try/catch it like a normal call.
  • Assuming the compiler is automatic everywhere — it's opt-in via the build plugin.
  • Still reaching for forwardRef — unnecessary for function components in React 19.

Interviewer follow-ups

  • "How would you show a pending state during submission?"useActionState's pending flag, or useFormStatus in a nested button.
  • "When do you still need useMemo?" — edge cases the compiler can't memoize, or when the compiler isn't enabled.
  • "How does this relate to debouncing input?" — Actions/transitions handle CPU-bound render work; network throttling still needs debouncing.

Sources

Key takeaways

  • Actions wire async work into forms: <form action={fn}> + useActionState for result/pending, useFormStatus for nested submit state.
  • The use() hook reads a promise (suspends) or context — and unlike useContext it can be called conditionally.
  • The React Compiler auto-memoizes, so manual useMemo/useCallback/memo become the exception, not the rule.
  • ref is now a normal prop in React 19 — forwardRef is no longer needed for function components.
  • useOptimistic shows an optimistic result immediately and reconciles when the real action resolves.

Frequently asked questions

What are Actions in React 19?

Actions are async functions wired directly into the form's action prop: <form action={fn}>. Combined with useActionState you get the return value, a pending flag, and automatic form reset on success; useFormStatus lets nested components read the pending state without prop drilling. They replace a lot of manual onSubmit/preventDefault plus useState boilerplate.

What does the use() hook do?

use() reads the value of a resource. Given a promise it suspends the component until the promise resolves (pairing with Suspense); given a context it returns the context value. Crucially, unlike useContext, use() may be called inside conditions and loops, so you can read context or a promise conditionally.

Do I still need useMemo and useCallback in React 19?

Much less. The React Compiler analyses your components and memoizes automatically, so most manual useMemo/useCallback/React.memo becomes unnecessary. You still reach for them in edge cases the compiler can't prove safe, or when the compiler isn't enabled.

By Mohammad Wasi

Software Engineering Leader & Technical Author · Updated August 26, 2026


Related Posts