The TypeScript error you're encountering stems from a common strictness issue in how TypeScript handles generic types, particularly when void is involved in a union within a Promise. While your original action function signature is logically sound, the @types/react definition for useActionState might have a specific expectation that Promise<T> isn't implicitly assignable to Promise<T | void>.
Here's how to correctly type your action function to resolve this, prioritizing the solutions.
1. Update Dependencies (Recommended First Step)
First, ensure your react, react-dom, and @types/react dependencies are updated to their latest canary/experimental versions for React 19. Type definitions for new, experimental features can evolve quickly, and an outdated type definition is a frequent cause of such errors.
1# For npm
2npm install react@next react-dom@next @types/react@next @types/react-dom@next
3
4# For yarn
5yarn add react@next react-dom@next @types/react@next @types/react-dom@next
1# For npm
2npm install react@next react-dom@next @types/react@next @types/react-dom@next
3
4# For yarn
5yarn add react@next react-dom@next @types/react@next @types/react-dom@next
After updating, restart your TypeScript server or IDE (e.g., VS Code) and check if the error persists. If the error still shows, proceed to the solution below.
2. Adjust the Action Function's Return Type
If updating dependencies doesn't resolve the issue, you need to explicitly broaden the return type of your async action function to include void within the Promise's generic parameter. This satisfies the strict type checker based on the error message's expectation of Promise<void | FormState>.
1// app/actions/submitFormAction.ts
2"use server";
3
4interface FormState {
5 success: boolean;
6 message: string;
7}
8
9// Corrected signature to match the expected Promise return type from the error message
10async function submitFormAction(
11 prevState: FormState,
12 formData: FormData
13): Promise<FormState | void> { // <-- Key change: added `| void` to the Promise generic
14 const email = formData.get("email");
15
16 if (!email) {
17 // Explicitly return FormState; it is compatible with FormState | void
18 return { success: false, message: "Email is required." };
19 }
20
21 // Explicitly return FormState; it is compatible with FormState | void
22 return { success: true, message: "Subscribed successfully!" };
23}
24
25export { submitFormAction, type FormState }; // Export FormState for client component
1// app/actions/submitFormAction.ts
2"use server";
3
4interface FormState {
5 success: boolean;
6 message: string;
7}
8
9// Corrected signature to match the expected Promise return type from the error message
10async function submitFormAction(
11 prevState: FormState,
12 formData: FormData
13): Promise<FormState | void> { // <-- Key change: added `| void` to the Promise generic
14 const email = formData.get("email");
15
16 if (!email) {
17 // Explicitly return FormState; it is compatible with FormState | void
18 return { success: false, message: "Email is required." };
19 }
20
21 // Explicitly return FormState; it is compatible with FormState | void
22 return { success: true, message: "Subscribed successfully!" };
23}
24
25export { submitFormAction, type FormState }; // Export FormState for client component
Explanation of the Fix:
By changing the return type to Promise<FormState | void>, you explicitly tell TypeScript that the promise will resolve to either a FormState object or void. Since your function always returns FormState, and FormState is compatible with FormState | void, this satisfies the type checker's expectation for Promise<void | FormState>.
Your Client Component (Remains Unchanged)
Your client component code for useActionState remains correct as it is, provided submitFormAction and FormState are properly imported:
1// app/components/SubscribeForm.tsx
2"use client";
3
4import { useActionState } from "react";
5// Import both the action and the FormState interface
6import { submitFormAction, type FormState } from "../actions/submitFormAction";
7
8export default function SubscribeForm() {
9 // TypeScript will now correctly infer types or you can explicitly provide them
10 const [state, formAction, isPending] = useActionState<FormState, FormData>(
11 submitFormAction,
12 { success: false, message: "" }
13 );
14
15 return (
16 <form action={formAction}>
17 <input type="email" name="email" className="border p-2" />
18 <button type="submit" disabled={isPending}>
19 {isPending ? "Submitting..." : "Subscribe"}
20 </button>
21 {state.message && <p>{state.message}</p>}
22 </form>
23 );
24}
1// app/components/SubscribeForm.tsx
2"use client";
3
4import { useActionState } from "react";
5// Import both the action and the FormState interface
6import { submitFormAction, type FormState } from "../actions/submitFormAction";
7
8export default function SubscribeForm() {
9 // TypeScript will now correctly infer types or you can explicitly provide them
10 const [state, formAction, isPending] = useActionState<FormState, FormData>(
11 submitFormAction,
12 { success: false, message: "" }
13 );
14
15 return (
16 <form action={formAction}>
17 <input type="email" name="email" className="border p-2" />
18 <button type="submit" disabled={isPending}>
19 {isPending ? "Submitting..." : "Subscribe"}
20 </button>
21 {state.message && <p>{state.message}</p>}
22 </form>
23 );
24}
By applying the recommended dependency updates first, and then the explicit Promise<FormState | void> return type if necessary, you should resolve the TypeScript compilation error.