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

NextJS 14中useActionState类型错误求助

NextJS 14 useActionState类型不匹配问题解决

问题概述

构建NextJS 14应用时,使用useActionState调用createBook函数出现重载不匹配错误:createBook的prevState参数被定义为[State]数组类型,与useActionState期望的单个State对象类型不兼容。

错误信息

Overload 1 of 2, '(action: (state: { errors: { image_url?: string[] | undefined; bookid?: string[] | undefined; shelf?: string[] | undefined; title?: string[] | undefined; author?: string[] | undefined; publication_year?: string[] | undefined; ... 4 more ...; total_copies?: 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: { image_url?: string[] | undefined; bookid?: string[] | undefined; shelf?: string[] | undefined; title?: string[] | undefined; ... 6 more ...; total_copies?: string[] | undefined; }; message: string; } | { ...; }>' is not assignable to parameter of type '(state: { errors: { image_url?: string[] | undefined; bookid?: string[] | undefined; shelf?: string[] | undefined; title?: string[] | undefined; author?: string[] | undefined; publication_year?: string[] | undefined; ... 4 more ...; total_copies?: string[] | undefined; }; message: string; } | { ...; }) => { ...; } |...'.
      Target signature provides too few arguments. Expected 2 or more, but got 1.
  Overload 2 of 2, '(action: (state: { errors: { image_url?: string[] | undefined; bookid?: string[] | undefined; shelf?: string[] | undefined; title?: string[] | undefined; author?: string[] | undefined; publication_year?: string[] | undefined; ... 4 more ...; total_copies?: string[] | undefined; }; message: string; } | { ...; }, payload: FormData) => { ...; } | ... 1 more ... | Promise<...>, initialState: { ...; } | { ...; }, permalink?: string | undefined): [state: ...]', gave the following error.
    Argument of type '(prevState: [State], formData: FormData) => Promise<{ errors: { image_url?: string[] | undefined; bookid?: string[] | undefined; shelf?: string[] | undefined; title?: string[] | undefined; ... 6 more ...; total_copies?: string[] | undefined; }; message: string; } | { ...; }>' is not assignable to parameter of type '(state: { errors: { image_url?: string[] | undefined; bookid?: string[] | undefined; shelf?: string[] | undefined; title?: string[] | undefined; author?: string[] | undefined; publication_year?: string[] | undefined; ... 4 more ...; total_copies?: string[] | undefined; }; message: string; } | { ...; }, payload: Form...'.
      Types of parameters 'prevState' and 'state' are incompatible.
        Type '{ errors: { image_url?: string[] | undefined; bookid?: string[] | undefined; shelf?: string[] | undefined; title?: string[] | undefined; author?: string[] | undefined; publication_year?: string[] | undefined; ... 4 more ...; total_copies?: string[] | undefined; }; message: string; } | { ...; }' is not assignable to type '[State]'.
          Type '{ errors: { image_url?: string[] | undefined; bookid?: string[] | undefined; shelf?: string[] | undefined; title?: string[] | undefined; author?: string[] | undefined; publication_year?: string[] | undefined; ... 4 more ...; total_copies?: string[] | undefined; }; message: string; }' is not assignable to type '[State]'.

  10 | export default function Form() {
 11 |   const initialState: State = { message: null, errors: {} };
> 12 |   const [State, formAction] = useActionState(createBook, initialState);
     |                                              ^
 13 |   return (
 14 |     <form action={formAction}>
 15 |       <div className="rounded-md bg-gray-50 p-4 md:p-6">
 ELIFECYCLE  Command failed with exit code 1.

创建书籍表单代码

// create-form.tsx
"use client";

import { createBook, State } from '@/app/lib/book-actions';
import { useActionState } from 'react';
import Link from 'next/link';
import { bookFormFields } from '@/app/lib/book-form-fields';
import { Button } from '@/app/ui/button';

export default function Form() {
  const initialState: State = { message: null, errors: {} };
  const [State, formAction] = useActionState(createBook, initialState);
  return (
    <form action={formAction}>
      <div className="rounded-md bg-gray-50 p-4 md:p-6">
        {bookFormFields.map((field) => (
          <div className="mb-4" key={field.id}>
            <label htmlFor={field.id} className="mb-2 block text-sm font-medium">
              {field.label}
            </label>
            <div className="relative mt-2 rounded-md">
              {field.type === "checkbox" ? (
                <input
                  id={field.id}
                  name={field.id}
                  type="checkbox"
                  className="peer block h-5 w-5 rounded border-gray-200 text-blue-600 focus:ring-2 focus:ring-blue-200"
                  aria-describedby={`${field.id}-error`}
                />
              ) : (
                <input
                  id={field.id}
                  name={field.id}
                  type={field.type}
                  placeholder={field.placeholder}
                  className="peer block w-full rounded-md border border-gray-200 py-2 px-3 text-sm placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-200"
                  aria-describedby={`${field.id}-error`}
                />
              )}
            </div>
            <div id={`${field.id}-error`} aria-live="polite" aria-atomic="true">
              {State.errors?.[field.id] &&
                State.errors[field.id].map((error: string) => (
                  <p className="mt-2 text-sm text-red-500" key={error}>
                    {error}
                  </p>
                ))}
            </div>
          </div>
        ))}
      </div>

      <div className="mt-6 flex justify-end gap-4">
        <Link
          href="/dashboard/invoices"
          className="flex h-10 items-center rounded-lg bg-gray-100 px-4 text-sm font-medium text-gray-600 transition-colors hover:bg-gray-200"
        >
          Cancel
        </Link>
        <Button type="submit">Create Book</Button>
      </div>
    </form>
  );
}

创建书籍的Action函数代码

'use server';
import { z } from 'zod';
import { sql } from '@vercel/postgres';
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
import { signIn } from '@/auth';
import { AuthError } from 'next-auth';

// 表单验证Schema
const FormSchema = z.object({
    id: z.string(),
    bookid: z.string({
        invalid_type_error: "请输入图书编号",
    }),
    shelf: z.string({
        invalid_type_error: "请输入书架位置",
    }),
    title: z.string({
        invalid_type_error: "请输入书名",
    }),
    author: z.string({
        invalid_type_error: "请输入作者",
    }),
    publication_year: z.number({
        invalid_type_error: "请输入出版年份",
    }),
    genre: z.string({
        invalid_type_error: "请输入图书分类",
    }),
    price: z.coerce.number()
        .gt(0, { message: '请输入大于0的价格' }),
    language: z.string({
        invalid_type_error: "请输入语言",
    }),
    image_url: z.string({
        invalid_type_error: "请输入封面图片URL",
    }),
    in_stock: z.boolean(),
    total_copies: z.number(),
    date: z.string(),
});


export async function authenticate(
    prevState: { message: string },
    formData: FormData,
) {
    try {
        await signIn('credentials', formData);
    } catch (error) {
        if (error instanceof AuthError) {
            switch (error.type) {
                case 'CredentialsSignin':
                    return '登录凭证无效';
                default:
                    return '登录出错';
            }
        }
        throw error;
    }
}


const CreateBook = FormSchema.omit({ id: true, date: true });
const UpdateBook = FormSchema.omit({ id: true, date: true });

// 状态类型定义
export type State = {
    errors?: {
        bookid?: string[];
        shelf?: string[];
        title?: string[];
        author?: string[];
        publication_year?: number[];
        genre?: string[];
        price?: number[];
        language?: string[];
        image_url?: string[];
        in_stock?: boolean[];
        total_copies?: number[];
    };
    message?: string | null;
};

export async function createBook(prevState: [State], formData: FormData) {
    // 验证表单数据
    const validatedFields = CreateBook.safeParse({
        bookid: formData.get('bookid'),
        shelf: formData.get('shelf'),
        title: formData.get('title'),
        author: formData.get('author'),
        publication_year: Number(formData.get('publication_year')),
        genre: formData.get('genre'),
        price: Number(formData.get('price')),
        language: formData.get('language'),
        image_url: formData.get('image_url'),
        in_stock: Boolean(formData.get('in_stock')),
        total_copies: Number(formData.get('total_copies')),
    });

    // 验证失败返回错误信息
    if (!validatedFields.success) {
        return {
            errors: validatedFields.error.flatten().fieldErrors,
            message: '字段缺失,创建图书失败',
        };
    }

    // 准备数据库插入数据
    const { bookid,
        shelf,
        title,
        author,
        publication_year,
        genre,
        price,
        language,
        image_url,
        in_stock,
        total_copies } = validatedFields.data;
    const date = new Date().toISOString().split('T')[0];

    // 插入数据库
    try {
        await sql`
        INSERT INTO books (
        bookid,
        shelf,
        title,
        author,
        publication_year,
        genre,
        price,
        language,
        image_url,
        in_stock,
        total_copies)
        VALUES (
        ${bookid},
        ${shelf},
        ${title},
        ${author},
        ${publication_year},
        ${genre},
        ${price},
        ${language},
        ${image_url},
        ${in_stock},
        ${total_copies})
      `;
    } catch (error) {
        // 数据库错误返回提示
        return {
            message: '数据库错误,创建图书失败',
        };
    }

    // 刷新缓存并跳转
    revalidatePath('/dashboard/books');
redirect('/dashboard/books');
}

解决方案

1. 修正createBook函数的prevState参数类型

useActionState传递给action函数的是单个State对象,而非数组。将参数类型从[State]改为State:

// 修改前
export async function createBook(prevState: [State], formData: FormData) { ... }

// 修改后
export async function createBook(prevState: State, formData: FormData) { ... }

2. 修正表单组件的变量名冲突

表单中const [State, formAction]的State与导入的类型State重名,导致类型混淆,改为小写变量名:

// 修改前
const [State, formAction] = useActionState(createBook, initialState);

// 修改后
const [state, formAction] = useActionState(createBook, initialState);

同时将后续使用State的地方全部替换为state:

{state.errors?.[field.id] &&
  state.errors[field.id].map((error: string) => (
    <p className="mt-2 text-sm text-red-500" key={error}>
      {error}
    </p>
  ))}

3. 修正State类型定义的错误字段类型

State类型中,publication_year、price、in_stock、total_copies的错误类型被定义为number[]/boolean[],但Zod验证返回的错误信息都是字符串数组,需统一改为string[]:

// 修改前
export type State = {
    errors?: {
        bookid?: string[];
        shelf?: string[];
        title?: string[];
        author?: string[];
        publication_year?: number[];
        genre?: string[];
        price?: number[];
        language?: string[];
        image_url?: string[];
        in_stock?: boolean[];
        total_copies?: number[];
    };
    message?: string | null;
};

// 修改后
export type State = {
    errors?: {
        bookid?: string[];
        shelf?: string[];
        title?: string[];
        author?: string[];
        publication_year?: string[];
        genre?: string[];
        price?: string[];
        language?: string[];
        image_url?: string[];
        in_stock?: string[];
        total_copies?: string[];
    };
    message?: string | null;
};

完成以上修改后,类型不匹配的错误即可解决。

内容的提问来源于stack exchange,提问作者Haseeb Jan Hamraz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 22:19:52