You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NextJS中useActionState的TypeScript类型不匹配错误求助

解决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'],
});

错误原因分析

  1. State类型字段名不匹配:actions.ts中State的errors属性名(如eventLocation)和Zod schema的字段名(如eventLocations)不一致,导致TypeScript无法正确推断类型。
  2. initialState类型冲突:initialState的message设为null,但useActionState根据createEvent的返回值推断message为string类型,引发类型不兼容。
  3. formData字段映射错误:createEvent中解析表单数据时,字段名和Zod schema不对应(如eventLocation对应formData.get('eventLocations')),导致验证后字段名不符。
  4. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 10:36:06