Topic 53 of 54
Form State with useActionState
Overview
When using Server Actions, you still need to show loading states, success messages, or validation errors returned from the server. React 19 introduces `useActionState` (formerly `useFormState` in canary) to perfectly bridge the gap between a Server Action and the client UI state.
Syntax
The server action `submitFeedback` must be written to return an object (like `{ error: 'Too short' }`). `useActionState` captures that return value and updates the `state` variable automatically.
Managing Action State
jsx
import { useActionState } from 'react';
import { submitFeedback } from './actions'; // A Server Action
function FeedbackForm() {
// useActionState takes the action and an initial state
// It returns: [currentState, wrappedAction, isPending]
const [state, formAction, isPending] = useActionState(submitFeedback, {
message: null,
error: null
});
return (
<form action={formAction}>
<textarea name="feedback" />
{/* Show server response */}
{state.error && <p className="error">{state.error}</p>}
{state.message && <p className="success">{state.message}</p>}
<button type="submit" disabled={isPending}>
{isPending ? 'Submitting...' : 'Send'}
</button>
</form>
);
}Common Pitfalls
- Returning undefined from the Server Action. It must return a serializable state object that matches the shape of your initial state.
Interview Tips
- Alongside `useActionState`, mention `useFormStatus`. It's another React 19 hook that can be used inside a deeply nested submit button to check if the parent form `isPending`, without passing props.
Real-World Example
Returning field-level validation errors from the server and displaying them seamlessly under the correct inputs.
example
jsx
export async function registerUser(prevState, formData) {
const email = formData.get('email');
const password = formData.get('password');
const errors = {};
if (!email.includes('@')) errors.email = "Invalid email format.";
if (password.length < 8) errors.password = "Password too short.";
if (Object.keys(errors).length > 0) {
return { errors }; // Pass errors back to the client
}
await db.createUser({ email, password });
return { success: "User created!" };
}