React 表單與校驗提示詞(React Hook Form + Zod:前後端共用校驗規則、聯動字段、錯誤提示與提交狀態)

用 React 做註冊、下單、資料編輯這類表單,字段多、有聯動校驗、還要和後端校驗保持一致時用:描述字段和規則,得到用 Zod 定義校驗規則、React Hook Form 管理表單狀態的完整代碼,包括跨字段校驗、後端錯誤回填和無障礙的錯誤提示。

NNathaniel bigo··原創首發·AI 輔助撰寫
通用大模型 對話模型通用
你是一名熟悉 React Hook Form 和 Zod 的前端工程師。請幫我實現一個表單。

- 表單用途:[如企業開票資料填寫]
- 字段與規則(字段名、類型、是否必填、格式要求):
  [字段與規則]
- 字段之間的聯動:[字段之間的聯動](例:選擇「企業」時稅號必填,選擇「個人」時隱藏稅號)
- 提交接口與可能的錯誤返回:[提交接口與可能的錯誤返回](例:POST /api/invoices,返回字段級錯誤)
- UI 組件庫:[UI 組件庫](例:shadcn/ui、Ant Design、原生元素)
- 是否需要與後端共用校驗規則:[是/否]

要求:
1. 用 Zod 定義校驗規則,並從規則推導出 TypeScript 類型,不要重複手寫類型。
2. 錯誤提示用中文,具體到如何修正(例如「稅號應為 15 到 20 位字母或數字」,而不是「格式錯誤」)。
3. 聯動校驗:跨字段規則(如條件必填、兩次密碼一致、結束日期晚於開始日期)放在整體校驗中,並把錯誤掛到具體字段上;被隱藏的字段不參與校驗。
4. 表單狀態:校驗的觸發時機(失去焦點時校驗,提交後改為輸入時實時校驗);提交中禁用按鈕,防止重複提交;提交成功後的處理。
5. 後端錯誤:把接口返回的字段級錯誤回填到對應字段,把非字段錯誤顯示在表單頂部。
6. 無障礙:每個輸入框關聯標籤;錯誤訊息通過屬性關聯到輸入框,並標記字段為無效;提交失敗後焦點移到第一個出錯的字段。
7. 如果需要與後端共用:說明如何把校驗規則放到共享模組中,後端用同一份規則再校驗一次(前端校驗只為體驗,後端校驗才是安全保障)。

輸出:校驗規則文件、表單組件代碼、一份測試(至少覆蓋必填、聯動必填、後端錯誤回填),代碼帶中文註釋。

高亮處換成你自己的內容:[如企業開票資料填寫]、[字段與規則]、[字段之間的聯動]、[提交接口與可能的錯誤返回]、[UI 組件庫]、[是/否]

ChatGPT Plus 充值

已被複製 0 次

使用說明

怎麼填變量:[字段與規則] 盡量寫全,包括格式和長度。[字段之間的聯動] 是表單中最容易出錯的部分,比如「選了企業才顯示稅號」,要寫清楚隱藏時是否清空、是否校驗。

常見坑:

  • 只做前端校驗。前端校驗可以被繞過,後端必須用同樣的規則再校驗一次。用 Zod 的好處之一,就是可以前後端共用同一份規則。
  • 被隱藏的字段仍然參與校驗,導致用戶看不到錯誤卻無法提交。
  • 錯誤提示寫「格式錯誤」「輸入有誤」,用戶不知道該怎麼改。

追問技巧:追問「把這個表單拆成三步的分步表單,每一步只校驗當前步驟的字段」,或「加上草稿自動儲存,刷新頁面後恢復已填寫的內容」。

示例輸出

示例,僅供參考(校驗規則節選)
ts
import { z } from 'zod'

export const invoiceSchema = z
  .object({
    type: z.enum(['company', 'personal']),
    title: z.string().trim().min(1, '請填寫發票抬頭').max(100, '發票抬頭不超過 100 個字'),
    taxId: z.string().trim().optional(),
    email: z.string().email('請填寫正確的電郵,用於接收電子發票'),
  })
  .superRefine((v, ctx) => {
    if (v.type === 'company' && !/^[A-Z0-9]{15,20}$/i.test(v.taxId ?? '')) {
      ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['taxId'], message: '稅號應為 15 到 20 位字母或數字' })
    }
  })

export type InvoiceForm = z.infer<typeof invoiceSchema>

後端錯誤回填:

ts
const onSubmit = async (data: InvoiceForm) => {
  const res = await fetch('/api/invoices', { method: 'POST', body: JSON.stringify(data) })
  if (!res.ok) {
    const { fieldErrors } = await res.json()
    for (const [name, message] of Object.entries(fieldErrors ?? {})) {
      setError(name as keyof InvoiceForm, { message: String(message) })
    }
  }
}

同款作品

用這條提示詞做出來的作品;原作者會因此獲得積分

做同款

還沒有同款,來做第一個。

Nathaniel 的更多內容

同主題

同模型

0 條評論

登入 後參與評論

還沒有評論,來搶沙發~