Used to manage complex or interdependent state in function components, especially when useState
becomes unscalable.
useReducer()
const [state, dispatch] = useReducer(reducer, initialArg, init?)
Manages state transitions through a centralized reducer function and dispatch()
actions.
import { useReducer } from 'react';
function reducer(state, action) {
if (action.type === 'incremented_age') {
return {
age: state.age + 1
};
}
throw Error('Unknown action.');
}
export default function Counter() {
const [state, dispatch] = useReducer(reducer, { age: 42 });
return (
<>
<button onClick={() => {
dispatch({ type: 'incremented_age' })
}}>
Increment age
</button>
<p>Hello! You are {state.age}.</p>
</>
);
}
Use when:
useState
clutter (too many setters)useActionState()
const [state, formAction, isPending] = useActionState(actionFn, initialState, permalink?)
Simplifies reducer-style logic with async support and built-in form submission handling.
"use client";
import { useRef } from "react";
import { useActionState } from "react";
type FormState = {
success: boolean;
title?: string;
error?: string;
};
async function createPost(prevState: FormState, formData: FormData) {
"use server";
const title = formData.get("title");
if (!title || typeof title !== "string" || title.trim() === "") {
return { success: false, error: "Title is required." };
}
try {
// await prisma.post.create({ data: { title } });
return { success: true, title };
} catch (error) {
return { success: false, error: "Failed to create post." };
}
}
export default function StatefulForm() {
const formRef = useRef<HTMLFormElement>(null);
const [state, formAction, isPending] = useActionState<FormState, FormData>(
async (prevState, formData) => {
const result = await createPost(prevState, formData);
if (result.success) {
formRef.current?.reset();
}
return result;
},
{ success: false }
);
return (
<form ref={formRef} action={formAction} className="space-y-4 max-w-md">
<input name="title" placeholder="Post title" className="border px-3 py-2 rounded w-full" />
<button
type="submit"
disabled={isPending}
className="bg-blue-600 text-white px-4 py-2 rounded"
>
{isPending ? "Submitting..." : "Create Post"}
</button>
{state.success && <p className="text-green-600">✅ Created: {state.title}</p>}
{state.error && <p className="text-red-500">⚠️ {state.error}</p>}
</form>
);
}
Features:
action(prevState, formData)
formAction
integrates with <button formAction={...}>
isPending
tracks async transition stateUse when: