NextJS中useActionState的TypeScript类型不匹配错误求助
问题背景
之前跟着NextJS教程使用useFormState,现在React将其重命名为useActionState,本以为只是名称变更、语法一致,但调用时出现TypeScript类型错误,错误提示如下:
No overload matches this call.
Overload 1 of 2, '(action: (state: { errors: { eventName?: string[] | undefined; eventLocations?: string[] | undefined; eventTypes?: string[] | undefined; eventStartDate?: string[] | undefined; eventEndDate?: string[] | undefined; eventHouses?: string[] | undefined; eventVictors?: string[] | undefined; eventLosers?: string[] | undefined; eventExtraNotes?: string[] | undefined; }; message: string; } | { ...; }) => { ...; } | ... 1 more ... | Promise<...>, initialState: { ...; } | { ...; }, permalink?: string | undefined): [state: ...]', gave the following error.
Argument of type '(prevState: State, formData: FormData) => Promise<{ errors: { eventName?: string[] | undefined; eventLocations?: string[] | undefined; eventTypes?: string[] | undefined; eventStartDate?: string[] | undefined; ... 4 more ...; eventExtraNotes?: string[] | undefined; }; message: string; } | { ...; }>' is not assignable to parameter of type '(state: { errors: { eventName?: string[] | undefined; eventLocations?: string[] | undefined; eventTypes?: string[] | undefined; eventStartDate?: string[] | undefined; eventEndDate?: string[] | undefined; eventHouses?: string[] | undefined; eventVictors?: string[] | undefined; eventLosers?: string[] | undefined; even...'.
Target signature provides too few arguments. Expected 2 or more, but got 1.
Overload 2 of 2, '(action: (state: { errors: { eventName?: string[] | undefined; eventLocations?: string[] | undefined; eventTypes?: string[] | undefined; eventStartDate?: string[] | undefined; eventEndDate?: string[] | undefined; eventHouses?: string[] | undefined; eventVictors?: string[] | undefined; eventLosers?: string[] | undefined; eventExtraNotes?: string[] | undefined; }; message: string; } | { ...; }, payload: FormData) => { ...; } | ... 1 more ... | Promise<...>, initialState: { ...; } | { ...; }, permalink?: string | undefined): [state: ...]', gave the following error.
Argument of type '{ message: null; errors: {}; }' is not assignable to parameter of type '{ errors: { eventName?: string[] | undefined; eventLocations?: string[] | undefined; eventTypes?: string[] | undefined; eventStartDate?: string[] | undefined; eventEndDate?: string[] | undefined; eventHouses?: string[] | undefined; eventVictors?: string[] | undefined; eventLosers?: string[] | undefined; eventExtraNo...'.
Type '{ message: null; errors: {}; }' is not assignable to type '{ errors: { eventName?: string[] | undefined; eventLocations?: string[] | undefined; eventTypes?: string[] | undefined; eventStartDate?: string[] | undefined; eventEndDate?: string[] | undefined; eventHouses?: string[] | undefined; eventVictors?: string[] | undefined; eventLosers?: string[] | undefined; eventExtraNo...'.
Types of property 'message' are incompatible.
Type 'null' is not assignable to type 'string'.
相关代码
events-page.tsx
'use client'; import { House, Type, Location } from '@/data/definitions'; import { useActionState, useEffect, useState } from 'react'; import { EventSubmission } from '@/lib/definitions'; import { createEvent } from '@/lib/actions'; export default function EventsForm({ houses, types, locations }: { houses: House[]; types: Type[]; locations: Location[]; }) { const initialState = { message: null, errors: {} }; const [state, dispatch] = useActionState(createEvent, initialState); const [formData, setFormData] = useState<EventSubmission>({ eventName: '', eventLocations: '', eventTypes: '', eventStartDate: '', eventEndDate: '', eventHouses: '', eventVictors: '', eventLosers: '', eventExtraNotes: '' }); const handleChange = (e: any) => { const { name, value, type } = e.target; let valueToSubmit = value; if (type === 'date') { const [year, month, day] = value.split('-').map((num: string) => parseInt(num, 10)); if (year && month && day) { const formattedYear = year.toString().padStart(4, '0'); const formattedMonth = (month).toString().padStart(2, '0'); const formattedDay = day.toString().padStart(2, '0'); valueToSubmit = `${formattedYear}-${formattedMonth}-${formattedDay}`; } } setFormData({ ...formData, [name]: valueToSubmit }); console.log(formData); } useEffect(() => { console.log(formData); }, [formData]); return ( <> <div className="admin-intro"> <h2>Add a new Event</h2> <p>Submit a new event to the database.</p> </div> <form className="admin-form" action={dispatch}> <div className="admin-form-main"> <div className="admin-input"> <label htmlFor="eventName">Event Name</label> <input id="eventName" type="text" name="eventName" placeholder="Event Name..." value={formData.eventName} onChange={handleChange} /> {state?.errors?.eventName && state.errors.eventName.map((error) => ( <p key={error} className="form__input--error">{error}</p> ))} </div> --- More Inputs --- </div> <div className="admin-submit"> <button type="submit">Submit</button> </div> </form> </> ) }
actions.ts
import { EventFormSchema } from "./schemas"; // Events export type State = { errors?: { eventName?: string[]; eventLocation?: string[]; eventType?: string[]; eventStartDate?: string[]; eventEndDate?: string[]; eventHouses?: string[]; eventVictors?: string[]; eventLosers?: string[]; eventExtraNotes?: string[]; }; message?: string | null; }; export async function createEvent(prevState: State, formData: FormData) { console.log(formData); const validatedFields = EventFormSchema.safeParse({ eventName: formData.get('eventName'), eventLocation: formData.get('eventLocations'), eventType: formData.get('eventTypes'), eventStartDate: formData.get('eventStartDate'), eventEndDate: formData.get('eventEndDate'), eventHouses: formData.get('eventHouses'), eventVictors: formData.get('eventVictors'), eventLosers: formData.get('eventLosers'), eventExtraNotes: formData.get('eventNotes') }); if (!validatedFields.success) { return { errors: validatedFields.error.flatten().fieldErrors, message: 'Missing or incorrect fields. Failed to create event.' }; } const { eventName, eventLocations, eventTypes, eventStartDate, eventEndDate, eventHouses, eventVictors, eventLosers, eventExtraNotes } = validatedFields.data; console.log(validatedFields.data); return { message: 'Event created successfully.' }; }
schemas.ts
import { z } from 'zod'; export const EventFormSchema = z.object({ eventName: z.string({ required_error: "Event name is required." }).min(1, "Event name is required."), eventLocations: z.string({ required_error: "Please select a location. If none exists, add a location beforehand." }).min(1, "Please select a location. If none exists, add a location beforehand."), eventTypes: z.string({ required_error: "Please select an event type. If none exists, please create one beforehand." }).min(1, "Please select an event type. If none exists, please create one beforehand."), eventStartDate: z.date().optional().nullable().transform(val => val ?? undefined).refine(val => !val, { message: "Event start date must be a valid date string.", }), eventEndDate: z.date().optional().nullable().transform(val => val ?? undefined).refine(val => !val, { message: "Event end date must be a valid date string.", }), eventHouses: z.union([z.string(), z.array(z.string())]).optional().nullable().transform(val => val ?? undefined).refine(val => { if (Array.isArray(val)) return val.every(house => typeof house === 'string'); return true; }, { message: "Event houses must be a string or an array of strings.", }), eventVictors: z.union([z.string(), z.array(z.string())]).optional().nullable().transform(val => val ?? undefined).refine(val => { if (Array.isArray(val)) return val.every(victor => typeof victor === 'string'); return true; }, { message: "Event victors must be a string or an array of strings.", }), eventLosers: z.union([z.string(), z.array(z.string())]).optional().nullable().transform(val => val ?? undefined).refine(val => { if (Array.isArray(val)) return val.every(loser => typeof loser === 'string'); return true; }, { message: "Event losers must be a string or an array of strings.", }), eventExtraNotes: z.string().optional().nullable().default("") }).refine(data => { if (data.eventStartDate && data.eventEndDate) { console.log(data.eventStartDate); console.log(data.eventEndDate); return new Date(data.eventEndDate) > new Date(data.eventStartDate); } return true; }, { message: "Event end date must be later than event start date.", path: ['eventEndDate'], });
错误原因分析
- State类型字段名不匹配:
actions.ts中State的errors属性名(如eventLocation)和Zod schema的字段名(如eventLocations)不一致,导致TypeScript无法正确推断类型。 - initialState类型冲突:
initialState的message设为null,但useActionState根据createEvent的返回值推断message为string类型,引发类型不兼容。 - formData字段映射错误:
createEvent中解析表单数据时,字段名和Zod schema不对应(如eventLocation对应formData.get('eventLocations')),导致验证后字段名不符。 - Zod日期验证逻辑错误:日期字段的
refine条件写反,无法正确验证有效日期。
解决步骤
1. 对齐State类型与Zod schema的字段名
修改actions.ts中的State类型,确保errors的属性名和Zod schema完全一致:
export type State = { errors?: { eventName?: string[]; eventLocations?: string[]; // 修正为eventLocations eventTypes?: string[]; // 修正为eventTypes eventStartDate?: string[]; eventEndDate?: string[]; eventHouses?: string[]; eventVictors?: string[]; eventLosers?: string[]; eventExtraNotes?: string[]; }; message?: string | null; };
2. 明确标注initialState的类型
在events-page.tsx中,给initialState加上State类型注解,避免TypeScript自动推断错误:
import { State, createEvent } from '@/lib/actions'; // ... const initialState: State = { message: null, errors: {} }; const [state, dispatch] = useActionState(createEvent, initialState);
3. 修正formData字段映射
修改actions.ts中safeParse的字段名,确保和Zod schema及表单字段一致:
const validatedFields = EventFormSchema.safeParse({ eventName: formData.get('eventName'), eventLocations: formData.get('eventLocations'), // 修正字段名 eventTypes: formData.get('eventTypes'), // 修正字段名 eventStartDate: formData.get('eventStartDate'), eventEndDate: formData.get('eventEndDate'), eventHouses: formData.get('eventHouses'), eventVictors: formData.get('eventVictors'), eventLosers: formData.get('eventLosers'), eventExtraNotes: formData.get('eventExtraNotes') // 修正为eventExtraNotes });
4. 修复Zod日期验证逻辑
修改schemas.ts中的日期字段验证,确保能正确校验有效日期:
eventStartDate: z.date().optional().nullable().transform(val => val ?? undefined).refine(val => { if (val) return !isNaN(new Date(val).getTime()); // 验证日期有效 return true; }, { message: "Event start date must be a valid date string.", }), eventEndDate: z.date().optional().nullable().transform(val => val ?? undefined).refine(val => { if (val) return !isNaN(new Date(val).getTime()); // 验证日期有效 return true; }, { message: "Event end date must be a valid date string.", }),
内容的提问来源于stack exchange,提问作者Cathair M

