KASHII UPDATEZ Everyday Student Requirements & Python Coding Tutorials by Python Kashi
KashiiUpdatez
← Back to Tech Blog

React 19 Deep Dive: Actions, useOptimistic, useFormStatus & Concurrent Rendering

React 19 is the biggest release since React 16 hooks. Actions replace useEffect form submissions, useOptimistic enables instant UI feedback, and React Compiler eliminates manual memoization. Complete guide with working code examples.

Kashinath Chavan
Kashinath Chavan
Frontend & Next.js ⏱️ 5 min read Aug 20, 2026
Follow β†—
React 19 Deep Dive: Actions, useOptimistic, useFormStatus & Concurrent Rendering

What's New in React 19

React 19 (stable Q1 2026) ships the most developer-impactful features since React 16 introduced hooks. The theme is Actions: a unified pattern for handling async mutations that eliminates the verbose useState + useEffect + loading/error state boilerplate that every React developer has written hundreds of times.

FeatureBefore (React 18)After (React 19)
Form submissionsuseState + useEffect + manual error handlingActions with built-in pending/error states
Optimistic UIManual state + rollback logicuseOptimistic hook
Form statusProp drilling or ContextuseFormStatus hook
MemoizationuseMemo + useCallback everywhereReact Compiler (auto-memoization)
Refs on function componentsforwardRef wrapperref as regular prop

1. Actions β€” The End of Form Boilerplate

React 18 β€” The Old Way

function UpdateNameForm() {
  const [name, setName] = useState('');
  const [isPending, setIsPending] = useState(false);
  const [error, setError] = useState<string | null>(null);

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    setIsPending(true);
    setError(null);
    try {
      await updateUserName(name);
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Unknown error');
    } finally {
      setIsPending(false);
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input value={name} onChange={e => setName(e.target.value)} />
      {error && <p className="error">{error}</p>}
      <button disabled={isPending}>{isPending ? 'Saving...' : 'Save'}</button>
    </form>
  );
}

React 19 β€” Actions

import { useActionState } from 'react';

async function updateNameAction(prevState: unknown, formData: FormData) {
  const name = formData.get('name') as string;
  if (!name || name.length < 2) {
    return { error: 'Name must be at least 2 characters' };
  }
  await updateUserName(name);
  return { success: true };
}

function UpdateNameForm() {
  const [state, submitAction, isPending] = useActionState(updateNameAction, null);

  return (
    <form action={submitAction}>
      <input name="name" />
      {state?.error && <p className="error">{state.error}</p>}
      {state?.success && <p className="success">Name updated!</p>}
      <SubmitButton />
    </form>
  );
}

2. useFormStatus β€” Smart Submit Buttons

import { useFormStatus } from 'react-dom';

function SubmitButton({ label = 'Submit' }: { label?: string }) {
  const { pending } = useFormStatus();

  return (
    <button type="submit" disabled={pending} aria-busy={pending}>
      {pending ? '<span className="spinner" /> Saving...' : label}
    </button>
  );
}

// Reuse across any form β€” automatically picks up the right pending state
function CreatePostForm() {
  const [state, action] = useActionState(createPostAction, null);
  return (
    <form action={action}>
      <input name="title" placeholder="Post title" />
      <textarea name="body" placeholder="Content..." />
      <SubmitButton label="Publish Post" />
    </form>
  );
}

3. useOptimistic β€” Instant UI Feedback

import { useOptimistic, useActionState } from 'react';

type Message = { id: string; text: string; status: 'sent' | 'pending' };

function ChatThread({ initialMessages }: { initialMessages: Message[] }) {
  const [optimisticMessages, addOptimisticMessage] = useOptimistic<Message[], string>(
    initialMessages,
    (currentMessages, newText) => [
      ...currentMessages,
      { id: Date.now().toString(), text: newText, status: 'pending' },
    ]
  );

  async function sendMessageAction(_: unknown, formData: FormData) {
    const text = formData.get('text') as string;
    addOptimisticMessage(text);  // Update UI immediately
    await sendMessage(text);     // Then actually send
  }

  const [, action] = useActionState(sendMessageAction, null);

  return (
    <>
      <div className="messages">
        {optimisticMessages.map(msg => (
          <div key={msg.id} style={{ opacity: msg.status === 'pending' ? 0.5 : 1 }}>
            {msg.text}
          </div>
        ))}
      </div>
      <form action={action}>
        <input name="text" autoFocus />
        <SubmitButton label="Send" />
      </form>
    </>
  );
}

4. React Compiler β€” Automatic Memoization

// Before Compiler β€” manual memoization
const ExpensiveList = React.memo(function ExpensiveList({ items, onSelect }) {
  const sortedItems = useMemo(
    () => [...items].sort((a, b) => a.name.localeCompare(b.name)),
    [items]
  );
  const handleSelect = useCallback((id: string) => onSelect(id), [onSelect]);
  return sortedItems.map(item => <Item key={item.id} item={item} onSelect={handleSelect} />);
});

// After Compiler β€” write normal code
function ExpensiveList({ items, onSelect }) {
  const sortedItems = [...items].sort((a, b) => a.name.localeCompare(b.name));
  return sortedItems.map(item => (
    <Item key={item.id} item={item} onSelect={() => onSelect(item.id)} />
  ));
}
Important: React Compiler requires components to follow the Rules of React (pure renders, no mutation of props/state). Components that violate the rules are automatically skipped by the compiler.

5. Refs as Regular Props

// React 18 β€” required forwardRef wrapper
const Input = React.forwardRef<HTMLInputElement, { label: string }>(
  function Input({ label }, ref) {
    return <input ref={ref} aria-label={label} />;
  }
);

// React 19 β€” ref is just a prop
function Input({ label, ref }: { label: string; ref: React.Ref<HTMLInputElement> }) {
  return <input ref={ref} aria-label={label} />;
}

const inputRef = useRef<HTMLInputElement>(null);
<Input label="Username" ref={inputRef} />
Topics: #Computer Science #Frontend #Git #Hooks #Interview #Nextjs #React #React-19 #Scalability #Typescript
πŸ‘οΈ 3651 views

More from Frontend & Next.js

Chat Chat with Kashii