TopFrontDev logo
HomeVideosPlaylistsResourcesArticlesAbout
Youtube logoSubscribe

TopFrontDev

Front-end techniques that actually ship. Practical engineering you can use in production.

Content

  • Videos
  • Playlists
  • Articles
  • Resources

About

  • About
  • Sponsorship
  • Contact

Connect

Youtube logogithub logolinkedin logo

© 2026 TopFrontDev. All rights reserved.

TopFrontDev logo
HomeVideosPlaylistsResourcesArticlesAbout
Youtube logoSubscribe

React 19: Actions, Optimistic Updates, and the Future of Forms

December 15, 2025

React 19 introduces groundbreaking features that fundamentally change how we handle asynchronous operations, forms, and user interactions. The star of the show is the Actions API - a new way to handle async functions that automatically manages pending states, errors, and optimistic updates.

Actions: Async Functions in Transitions

React 19 allows you to pass async functions directly to startTransition, which automatically handles pending states, errors, and form submissions. No more manual setIsPending calls!

import { useTransition } from 'react';

function UpdateName({}) {
  const [name, setName] = useState("");
  const [error, setError] = useState(null);
  const [isPending, startTransition] = useTransition();

  const handleSubmit = () => {
    startTransition(async () => {
      const error = await updateName(name);
      if (error) {
        setError(error);
        return;
      }
      redirect("/path");
    })
  };

  return (
    <div>
      <input value={name} onChange={(event) => setName(event.target.value)} />
      <button onClick={handleSubmit} disabled={isPending}>
        Update
      </button>
      {error && <p>{error}</p>}
    </div>
  );
}

useActionState: Form State Management Made Simple

The useActionState hook provides a complete solution for form handling with automatic state management, error handling, and pending states.

import { useActionState } from 'react';

function ChangeName({ name, setName }) {
  const [error, submitAction, isPending] = useActionState(
    async (previousState, formData) => {
      const error = await updateName(formData.get("name"));
      if (error) {
        return error;
      }
      redirect("/path");
      return null;
    },
    null,
  );

  return (
    <form action={submitAction}>
      <input type="text" name="name" />
      <button type="submit" disabled={isPending}>Update</button>
      {error && <p>{error}</p>}
    </form>
  );
}

useOptimistic: Instant UI Updates

The useOptimistic hook enables optimistic UI updates - show changes immediately while the server request is in progress, then reconcile with the actual result.

import { useOptimistic } from 'react';

function ChangeName({currentName, onUpdateName}) {
  const [optimisticName, setOptimisticName] = useOptimistic(currentName);

  const submitAction = async formData => {
    const newName = formData.get("name");
    setOptimisticName(newName); // Update UI immediately
    const updatedName = await updateName(newName);
    onUpdateName(updatedName); // Reconcile with server response
  };

  return (
    <form action={submitAction}>
      <p>Your name is: {optimisticName}</p>
      <p>
        <label>Change Name:</label>
        <input
          type="text"
          name="name"
          disabled={currentName !== optimisticName}
        />
      </p>
    </form>
  );
}

Form Actions: Native HTML Forms Meet React

React 19 brings <form> actions to life, allowing you to use native HTML forms with React's powerful state management:

// Server Action (Next.js App Router)
'use server'

export async function updateUser(formData: FormData) {
  const name = formData.get('name');
  // Server logic here
  return { success: true };
}

// Client Component
function UserForm() {
  return (
    <form action={updateUser}>
      <input name="name" required />
      <button type="submit">Update</button>
    </form>
  );
}

Enhanced Transitions and Suspense

Improved startTransition

Transitions now accept async functions directly, eliminating the need for wrapping promises:

// React 18
startTransition(() => {
  fetchData().then(setData);
});

// React 19 - Direct async support
startTransition(async () => {
  const data = await fetchData();
  setData(data);
});

Better Suspense for Data Fetching

Suspense boundaries now work more predictably with Server Components and streaming:

function App() {
  return (
    <Suspense fallback={<LoadingSkeleton />}>
      <UserProfile />
    </Suspense>
  );
}

// Server Component with async data fetching
async function UserProfile() {
  const user = await fetchUser();
  return <Profile user={user} />;
}

useDeferredValue Enhancements

The useDeferredValue hook now integrates better with the new concurrent features:

import { useDeferredValue, useMemo } from 'react';

function SearchResults({ query }) {
  const deferredQuery = useDeferredValue(query);

  // Expensive computation is deferred
  const results = useMemo(() => {
    return searchData(deferredQuery);
  }, [deferredQuery]);

  return <ResultsList results={results} />;
}

Migration Guide: From React 18 to 19

1. Upgrade Dependencies

npm install react@19 react-dom@19
# Or with Next.js
npm install next@latest

2. Replace Manual Pending States

Before:

const [isPending, setIsPending] = useState(false);

const handleSubmit = async () => {
  setIsPending(true);
  try {
    await updateData();
  } finally {
    setIsPending(false);
  }
};

After:

const [isPending, startTransition] = useTransition();

const handleSubmit = () => {
  startTransition(async () => {
    await updateData();
  });
};

3. Convert Forms to Actions

Before:

function MyForm() {
  const [state, setState] = useState({});

  const handleSubmit = async (e) => {
    e.preventDefault();
    const formData = new FormData(e.target);
    await submitForm(formData);
  };

  return <form onSubmit={handleSubmit}>...</form>;
}

After:

function MyForm() {
  const [error, submitAction, isPending] = useActionState(
    async (prevState, formData) => {
      return await submitForm(formData);
    },
    null
  );

  return <form action={submitAction}>...</form>;
}

4. Add Optimistic Updates

function TodoList() {
  const [todos, setTodos] = useState([]);
  const [optimisticTodos, setOptimisticTodos] = useOptimistic(todos);

  const addTodo = async (text) => {
    // Optimistic update
    setOptimisticTodos(prev => [...prev, { text, pending: true }]);

    // Server update
    const newTodo = await createTodo(text);
    setTodos(prev => [...prev, newTodo]);
  };

  return (
    <ul>
      {optimisticTodos.map(todo => (
        <li key={todo.id} style={{ opacity: todo.pending ? 0.5 : 1 }}>
          {todo.text}
        </li>
      ))}
    </ul>
  );
}

Performance Improvements

Automatic Batching (Enhanced)

React 19 continues React 18's automatic batching improvements, now extending to all async operations within transitions.

Better Concurrent Rendering

The Actions API integrates seamlessly with React's concurrent features, providing better prioritization of updates.

Improved Server Components

Server Components now work better with Actions and Suspense, enabling more sophisticated server-side rendering patterns.

Best Practices for React 19

1. Use Actions for All Async Operations

// ✅ Good: Actions handle pending states automatically
const [isPending, startTransition] = useTransition();
startTransition(async () => {
  await doSomething();
});

// ❌ Avoid: Manual pending state management
const [isPending, setIsPending] = useState(false);

2. Leverage Optimistic Updates

// ✅ Good: Instant feedback
const [optimisticData, setOptimisticData] = useOptimistic(data);
setOptimisticData(newData); // Immediate UI update
await serverUpdate(newData); // Reconcile later

3. Prefer Form Actions Over Manual Handling

// ✅ Good: Declarative and automatic
<form action={serverAction}>
  <input name="field" />
</form>

// ❌ Avoid: Manual event handling
<form onSubmit={handleSubmit}>

4. Use useDeferredValue for Expensive Operations

const deferredValue = useDeferredValue(value);
// Expensive computation here won't block urgent updates

Experimental Features (React Labs)

View Transitions

Animate between different UI states:

import { ViewTransition } from 'react';

function App() {
  return (
    <ViewTransition>
      {/* Content that animates during transitions */}
    </ViewTransition>
  );
}

Activity Component

Pre-render content for better performance:

import { Activity } from 'react';

<Activity mode={isVisible ? 'visible' : 'hidden'}>
  <ExpensiveComponent />
</Activity>

Conclusion

React 19 represents the most significant update to React's programming model since Hooks. The Actions API eliminates boilerplate, useOptimistic provides instant UI feedback, and enhanced forms make complex interactions simple.

Start migrating your forms and async operations to use Actions - you'll be amazed at how much simpler your code becomes. The future of React development is here, and it's more delightful than ever!

What are you most excited about in React 19? Share your thoughts and migration experiences in the comments!

TopFrontDev

Front-end techniques that actually ship. Practical engineering you can use in production.

Content

  • Videos
  • Playlists
  • Articles
  • Resources

About

  • About
  • Sponsorship
  • Contact

Connect

Youtube logogithub logolinkedin logo

© 2026 TopFrontDev. All rights reserved.