React 19 Actions: The End of useReducer Boilerplate
How React 19 Actions and useActionState replace the verbose useReducer pattern for form handling and async state management.
If you've been writing React for any length of time, you know the useReducer dance: define action types, write a reducer, dispatch actions, handle loading and error states manually. React 19 Actions finally give us a first-class alternative. Here's how to migrate — and why you should.
The Old Way: useReducer for Form State
This is what we've been writing for years:
// The useReducer approach — verbose but familiar
type State = {
status: 'idle' | 'loading' | 'success' | 'error';
error: string | null;
data: User | null;
};
type Action =
| { type: 'SUBMIT' }
| { type: 'SUCCESS'; payload: User }
| { type: 'ERROR'; payload: string };
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'SUBMIT':
return { ...state, status: 'loading', error: null };
case 'SUCCESS':
return { status: 'success', data: action.payload, error: null };
case 'ERROR':
return { ...state, status: 'error', error: action.payload };
}
}
function ProfileForm() {
const [state, dispatch] = useReducer(reducer, {
status: 'idle', error: null, data: null
});
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
dispatch({ type: 'SUBMIT' });
try {
const result = await updateProfile(new FormData(e.target as HTMLFormElement));
dispatch({ type: 'SUCCESS', payload: result });
} catch (err) {
dispatch({ type: 'ERROR', payload: (err as Error).message });
}
};
// ... render with state.status checks
}
That's 40+ lines before we even render anything. Every form in your app has some variation of this ceremony.
The React 19 Way: Actions + useActionState
React 19 introduces Actions — async functions that integrate with React's transition system. Combined with useActionState, the same form becomes:
"use client";
import { useActionState } from "react";
import { updateProfile } from "@/actions/user.actions";
function ProfileForm() {
const [state, formAction, isPending] = useActionState(
async (prevState: ActionState, formData: FormData) => {
const result = await updateProfile(formData);
if (result.error) return { error: result.error, success: false };
return { error: null, success: true, user: result.user };
},
{ error: null, success: false }
);
return (
<form action={formAction}>
<input name="name" required />
<input name="email" type="email" required />
{state.error && (
<p className="text-red-500">{state.error}</p>
)}
{state.success && (
<p className="text-green-500">Profile updated!</p>
)}
<button type="submit" disabled={isPending}>
{isPending ? "Saving..." : "Save Changes"}
</button>
</form>
);
}
No reducer. No action types. No manual dispatch. isPending is handled automatically. The form works without JavaScript (progressive enhancement).
What About useOptimistic?
React 19 also gives us useOptimistic for instant UI feedback. Pair it with Actions for the best user experience: update the UI immediately, let the server confirm or revert.
When useReducer Still Makes Sense
Actions don't replace every useReducer use case. Complex state machines with many transitions (think: multi-step wizards with branching logic, undo/redo systems, or game state) still benefit from explicit reducers. The rule of thumb: if your state transitions are simple and tied to a form submission, use Actions. If your state has complex internal logic with many possible transitions, keep useReducer.
The Migration Path
You don't need to rewrite everything at once. Start with your form components — they're the most common useReducer pattern and benefit the most from Actions. Leave complex state machines alone. Over time, you'll find that 80% of your reducers were just managing async form state, and Actions handle that natively.
React 19 Actions aren't just syntactic sugar. They integrate with Suspense, transitions, and progressive enhancement in ways that a manual useReducer never could. The boilerplate is gone. The DX is better. It's time to upgrade.
Admin
Cal.com
Open source scheduling — tự host booking system, thay thế Calendly. Free & privacy-first.
Bình luận (0)
Đăng nhập để bình luận
Chưa có bình luận nào. Hãy là người đầu tiên!