We use cookies to enhance your experience on the site
CodeWorlds

useFormStatus and useActionState - React 19 form hooks

On our cosmic journey through the galaxies of React, forms are one of the most important communication modules of the ship. Every mission report, every command for the navigation system, and every message to base - everything goes through forms. React 19 introduces two revolutionary hooks:

useFormStatus
and
useActionState
, which completely change the way we manage forms.

Server Actions - a new paradigm

Before we explore the new hooks, we need to understand the concept of Server Actions. These are functions marked with the

'use server'
directive that execute on the server side but can be called directly from forms in a client component:

1// actions.js
2'use server';
3
4async function submitMissionReport(formData) {
5  const missionName = formData.get('missionName');
6  const status = formData.get('status');
7
8  // This executes on the SERVER
9  await database.missions.create({
10    name: missionName,
11    status: status,
12    timestamp: new Date()
13  });
14
15  return { success: true, message: 'Report saved!' };
16}

Server Actions can be passed directly to the

action
attribute of a form:

1// MissionForm.jsx
2function MissionForm() {
3  return (
4    <form action={submitMissionReport}>
5      <input name="missionName" placeholder="Mission name" />
6      <select name="status">
7        <option value="active">Active</option>
8        <option value="completed">Completed</option>
9      </select>
10      <button type="submit">Submit report</button>
11    </form>
12  );
13}

This approach eliminates the need to manually create API endpoints and manage

loading
/
error
states - React handles this automatically.

useFormStatus - form status in child components

useFormStatus
is a hook that returns information about the form submission state. Key rule: it must be used in a child component of a
<form>
element
, not in the form itself.

1import { useFormStatus } from 'react-dom';
2
3// Submit button component - INSIDE the form
4function SubmitButton() {
5  const { pending, data, method, action } = useFormStatus();
6
7  return (
8    <button
9      type="submit"
10      disabled={pending}
11      style={{
12        padding: '10px 20px',
13        background: pending ? '#333' : '#00d4ff',
14        color: pending ? '#666' : '#0a0e17',
15        border: 'none',
16        borderRadius: '6px',
17        cursor: pending ? 'wait' : 'pointer'
18      }}
19    >
20      {pending ? 'Sending report...' : 'Submit mission report'}
21    </button>
22  );
23}
24
25// pending - true when the form is being submitted
26// data - FormData object with form data
27// method - HTTP method (GET/POST)
28// action - reference to the action function

Practical example with useFormStatus

1import { useFormStatus } from 'react-dom';
2
3function MissionInput({ name, label, placeholder }) {
4  const { pending } = useFormStatus();
5  return (
6    <div style={{ marginBottom: '12px' }}>
7      <label style={{ color: '#8899aa', display: 'block', marginBottom: '4px' }}>
8        {label}
9      </label>
10      <input
11        name={name}
12        placeholder={placeholder}
13        disabled={pending}
14        style={{
15          width: '100%',
16          padding: '8px 12px',
17          background: pending ? '#1a1a2e' : '#0f1729',
18          color: '#e0e1dd',
19          border: '1px solid #2a3a5c',
20          borderRadius: '6px',
21          opacity: pending ? 0.5 : 1
22        }}
23      />
24    </div>
25  );
26}
27
28function LoadingOverlay() {
29  const { pending } = useFormStatus();
30  if (!pending) return null;
31  return (
32    <div style={{
33      position: 'absolute', top: 0, left: 0, right: 0, bottom: 0,
34      background: 'rgba(10, 14, 23, 0.7)',
35      display: 'flex', alignItems: 'center', justifyContent: 'center',
36      borderRadius: '8px'
37    }}>
38      <p style={{ color: '#00d4ff' }}>Transmission in progress...</p>
39    </div>
40  );
41}
42
43function MissionReportForm({ submitAction }) {
44  return (
45    <form action={submitAction} style={{ position: 'relative' }}>
46      <LoadingOverlay />
47      <MissionInput name="mission" label="Mission name" placeholder="E.g. Alpha Centauri" />
48      <MissionInput name="coordinates" label="Coordinates" placeholder="E.g. 14h 29m 43s" />
49      <SubmitButton />
50    </form>
51  );
52}

useActionState - full action state management

useActionState
(formerly
useFormState
) is a hook that combines Server Actions with state management. It returns the current state, a wrapped action, and an
isPending
flag:

1import { useActionState } from 'react';
2
3// Server Action returning new state
4async function createMission(previousState, formData) {
5  const name = formData.get('name');
6  const destination = formData.get('destination');
7
8  if (!name || name.length < 3) {
9    return {
10      error: 'Mission name must be at least 3 characters',
11      success: false
12    };
13  }
14
15  // Simulation of saving to database
16  await new Promise(resolve => setTimeout(resolve, 1000));
17
18  return {
19    success: true,
20    message: 'Mission created successfully!',
21    missionId: Math.random().toString(36).substr(2, 9)
22  };
23}
24
25function CreateMissionForm() {
26  const [state, formAction, isPending] = useActionState(
27    createMission,
28    { error: null, success: false } // Initial state
29  );
30
31  return (
32    <form action={formAction}>
33      <h2 style={{ color: '#00d4ff' }}>New space mission</h2>
34
35      <input name="name" placeholder="Mission name" />
36      <input name="destination" placeholder="Destination" />
37
38      <button type="submit" disabled={isPending}>
39        {isPending ? 'Creating mission...' : 'Create mission'}
40      </button>
41
42      {state.error && (
43        <p style={{ color: '#ff6b6b' }}>{state.error}</p>
44      )}
45
46      {state.success && (
47        <p style={{ color: '#00ff88' }}>{state.message} (ID: {state.missionId})</p>
48      )}
49    </form>
50  );
51}

useActionState Parameters

The

useActionState
hook accepts three arguments:

  1. action - an async function
    (previousState, formData) => newState
  2. initialState - initial state (any object)
  3. permalink (optional) - URL for progressive enhancement

It returns an array with three elements:

  1. state - current state (result of last action or initialState)
  2. formAction - wrapped action to pass to
    <form action>
  3. isPending - boolean, whether the action is being executed

Progressive Enhancement

One of the greatest advantages of the new form hooks is progressive enhancement - forms work even without client-side JavaScript:

1// This form works BEFORE JavaScript loads!
2function SearchForm() {
3  const [state, searchAction, isPending] = useActionState(
4    async (prev, formData) => {
5      const query = formData.get('query');
6      const results = await searchGalaxy(query);
7      return { results, query };
8    },
9    { results: [], query: '' }
10  );
11
12  return (
13    <form action={searchAction}>
14      <input
15        name="query"
16        defaultValue={state.query}
17        placeholder="Search stars, planets, nebulae..."
18      />
19      <button type="submit" disabled={isPending}>
20        {isPending ? 'Scanning...' : 'Scan galaxy'}
21      </button>
22
23      {state.results.length > 0 && (
24        <ul>
25          {state.results.map(r => (
26            <li key={r.id}>{r.name} - {r.type}</li>
27          ))}
28        </ul>
29      )}
30    </form>
31  );
32}

A form submitted before JavaScript loads will be handled by the server, and after hydration React takes over control - the user will not notice any difference.

Form validation with useActionState

useActionState
is perfect for server-side validation with immediate feedback:

1async function validateAndSubmit(prevState, formData) {
2  const errors = {};
3
4  const name = formData.get('crewName');
5  const role = formData.get('role');
6  const experience = parseInt(formData.get('experience'));
7
8  if (!name || name.length < 2) errors.crewName = 'Name must be at least 2 characters';
9  if (!role) errors.role = 'Select a role';
10  if (isNaN(experience) || experience < 0) errors.experience = 'Invalid experience';
11
12  if (Object.keys(errors).length > 0) {
13    return { errors, success: false, values: { name, role, experience } };
14  }
15
16  await saveCrewMember({ name, role, experience });
17  return { errors: {}, success: true, message: 'Crew member added!' };
18}
19
20function CrewRegistrationForm() {
21  const [state, formAction, isPending] = useActionState(
22    validateAndSubmit,
23    { errors: {}, success: false }
24  );
25
26  return (
27    <form action={formAction}>
28      <input name="crewName" defaultValue={state.values?.name || ''} />
29      {state.errors?.crewName && <span style={{color:'red'}}>{state.errors.crewName}</span>}
30
31      <select name="role" defaultValue={state.values?.role || ''}>
32        <option value="">Select a role</option>
33        <option value="pilot">Pilot</option>
34        <option value="engineer">Engineer</option>
35        <option value="scientist">Scientist</option>
36      </select>
37      {state.errors?.role && <span style={{color:'red'}}>{state.errors.role}</span>}
38
39      <input name="experience" type="number" defaultValue={state.values?.experience || ''} />
40      {state.errors?.experience && <span style={{color:'red'}}>{state.errors.experience}</span>}
41
42      <button type="submit" disabled={isPending}>
43        {isPending ? 'Saving...' : 'Add to crew'}
44      </button>
45
46      {state.success && <p style={{color:'green'}}>{state.message}</p>}
47    </form>
48  );
49}

Summary

The new form hooks in React 19 are a fundamental change in form handling:

  1. useFormStatus
    - provides information about the form submission state in child components
  2. useActionState
    - combines Server Actions with state management, validation, and progressive enhancement
  3. Server Actions - server-side functions called directly from forms
  4. Progressive Enhancement - forms work before JavaScript loads

These hooks eliminate much of the boilerplate associated with form handling - you no longer need manual

onSubmit
,
useState
for each field, or a separate
isLoading
state. React 19 manages this automatically, like an autopilot on a spaceship.

Go to CodeWorlds