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

Next.js 14复用创建表单实现编辑功能的类型错误排查

解决Next.js 14中复用表单实现编辑功能的类型不匹配问题

问题根源

你当前的核心问题是错误地将Server Action返回的封装对象(包含info和success)直接传给了AboutForm的id属性,而id要求是number类型,自然会出现类型不匹配。另外可能存在Server Action命名混淆(比如editInfo如果是用来获取待编辑数据的,命名应该更清晰)。

具体解决方案

1. 拆分Server Action:区分数据获取与编辑操作

先把获取待编辑数据和执行编辑操作的逻辑分开,避免命名和功能混淆:

// app/actions.ts
'use server';

import prisma from '@/lib/prisma';

// 获取单条详情数据(用于编辑前加载)
export async function getInfoById(id: number) {
  try {
    const info = await prisma.info.findUnique({ where: { id } });
    return { success: true, info };
  } catch (err) {
    return { success: false, info: null, error: '加载数据失败' };
  }
}

// 执行编辑更新操作
export async function updateInfo(formData: FormData) {
  const id = Number(formData.get('id'));
  // 按你的表单字段提取数据,比如name、content等
  const name = formData.get('name') as string;
  const content = formData.get('content') as string;

  try {
    await prisma.info.update({
      where: { id },
      data: { name, content }
    });
    return { success: true };
  } catch (err) {
    return { success: false, error: '更新失败' };
  }
}

2. 改造AboutForm支持创建/编辑双模式

让表单组件通过可选的id属性区分创建和编辑状态,同时接收初始值填充表单:

// components/AboutForm.tsx
'use client';

import { useFormState } from 'react-dom';
import { createInfo, updateInfo } from '@/app/actions';

// 定义表单属性类型
type AboutFormProps = {
  id?: number; // 编辑时传number,创建时不传
  initialValues?: {
    name: string;
    content: string;
    // 其他表单字段
  };
};

export default function AboutForm({ id, initialValues }: AboutFormProps) {
  // 根据id选择对应的Server Action
  const action = id ? updateInfo : createInfo;
  const [state, formAction] = useFormState(action, { success: true });

  return (
    <form action={formAction}>
      {/* 编辑时传递隐藏的id字段,供updateAction使用 */}
      {id && <input type="hidden" name="id" value={id} />}
      
      {/* 表单字段,用initialValues填充默认值 */}
      <div>
        <label>名称</label>
        <input 
          name="name" 
          defaultValue={initialValues?.name} 
          required 
        />
      </div>
      <div>
        <label>内容</label>
        <textarea 
          name="content" 
          defaultValue={initialValues?.content} 
          required 
        />
      </div>

      {/* 状态提示 */}
      {!state.success && <p className="text-red-500">{state.error}</p>}
      
      <button type="submit">{id ? '更新信息' : '创建信息'}</button>
    </form>
  );
}

3. 在模态框/EditForm中正确传递数据

在触发编辑的模态框组件中,先调用getInfoById获取数据,提取出id和初始值后再传给AboutForm:

// components/EditModal.tsx
'use client';

import { useState, useEffect } from 'react';
import AboutForm from './AboutForm';
import { getInfoById } from '@/app/actions';

type EditModalProps = {
  isOpen: boolean;
  editId: number;
  onClose: () => void;
};

export default function EditModal({ isOpen, editId, onClose }: EditModalProps) {
  const [initialValues, setInitialValues] = useState<{ name: string; content: string } | null>(null);
  const [loading, setLoading] = useState(true);

  // 加载待编辑数据
  useEffect(() => {
    if (!isOpen) return;
    
    const fetchData = async () => {
      const result = await getInfoById(editId);
      if (result.success && result.info) {
        setInitialValues(result.info);
      }
      setLoading(false);
    };
    fetchData();
  }, [isOpen, editId]);

  if (!isOpen) return null;

  return (
    <div className="fixed inset-0 bg-black/50 flex items-center justify-center p-4">
      <div className="bg-white p-6 rounded-lg w-full max-w-md">
        <h3 className="text-lg font-bold mb-4">编辑信息</h3>
        
        {loading ? (
          <div>加载中...</div>
        ) : !initialValues ? (
          <div>数据不存在</div>
        ) : (
          <AboutForm id={editId} initialValues={initialValues} />
        )}
        
        <button 
          onClick={onClose}
          className="mt-4 px-4 py-2 bg-gray-200 rounded"
        >
          关闭
        </button>
      </div>
    </div>
  );
}

4. 触发模态框的调用示例

在页面中使用模态框,点击编辑按钮时传入对应的editId:

// app/page.tsx
'use client';

import { useState } from 'react';
import EditModal from '@/components/EditModal';

export default function Home() {
  const [isModalOpen, setIsModalOpen] = useState(false);
  const [currentEditId, setCurrentEditId] = useState<number | null>(null);

  const handleEdit = (id: number) => {
    setCurrentEditId(id);
    setIsModalOpen(true);
  };

  return (
    <div className="p-8">
      <h1 className="text-2xl font-bold mb-6">信息列表</h1>
      
      {/* 假设这是列表项,点击编辑按钮触发模态框 */}
      <div className="flex items-center justify-between mb-2 p-2 border">
        <span>测试信息1</span>
        <button 
          onClick={() => handleEdit(1)}
          className="px-3 py-1 bg-blue-500 text-white rounded"
        >
          编辑
        </button>
      </div>

      <EditModal 
        isOpen={isModalOpen} 
        editId={currentEditId!} 
        onClose={() => setIsModalOpen(false)} 
      />
    </div>
  );
}

关键注意事项

  • 永远不要直接把Server Action的返回对象(带success和info的结构)传给表单的id属性,必须提取info.id这个纯number值。
  • 处理数据加载和异常情况(比如数据不存在),避免组件渲染时出现类型错误。
  • 表单组件通过可选的id属性复用逻辑,无需额外动态导入(动态导入解决的是代码分割问题,和类型不匹配无关)。

内容的提问来源于stack exchange,提问作者Akinola

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 11:36:22