如何使用useFieldArray获取数组项的价格与数量值并计算总价?
解决React Hook Form useFieldArray动态表单总价计算问题
问题核心
你遇到的item is not defined错误是因为React Hook Form不会自动暴露item变量,表单字段值需要通过官方提供的API(如watch、getValues)获取,同时代码中存在字段路径不匹配的问题:useFieldArray的name是"items",但register时用的是item.${i}.xxx,导致表单值无法正确关联。
修复步骤
1. 修正字段注册路径
将所有register的字段名从item.${i}.xxx改为items.${i}.xxx,同时修正错误提示的路径,并且合并register的配置项(之前分开写的配置会被覆盖):
// 修正Item Name的register和错误提示 <StyledInput type="text" id="itemName" {...register(`items.${i}.name`, { required: "Can't be empty" })} /> <Error>{errors?.['items']?.[i]?.['name']?.['message']}</Error> // 修正Quantity的register和错误提示 <StyledInput type="text" id="itemsQuantity" {...register(`items.${i}.quantity`, { valueAsNumber: true, required: "Can't be empty" })} /> <Error>{errors?.['items']?.[i]?.['quantity']?.['message']}</Error> // 修正Price的register和错误提示 <StyledInput type="text" id="itemPrice" {...register(`items.${i}.price`, { valueAsNumber: true, required: "Can't be empty" })} /> <Error>{errors?.['items']?.[i]?.['price']?.['message']}</Error>
2. 用watch实时计算总价
watch可实时监听表单字段变化,适合需要实时更新总价的场景:
// 从useForm中解构watch const { control, register, watch, formState: { errors } } = useForm(); // 监听整个items数组 const items = watch("items"); // 渲染总价时计算 <StyledField> {Intl.NumberFormat('en-GB', { style: "currency", currency: 'GBP' }).format( // 处理空值避免NaN (items?.[i]?.price || 0) * (items?.[i]?.quantity || 0) )} </StyledField>
3. 用getValues获取当前值(非实时场景)
如果不需要实时更新,仅在需要时获取当前值,可使用getValues:
// 从useForm中解构getValues const { control, register, getValues, formState: { errors } } = useForm(); // 渲染总价时计算 <StyledField> {Intl.NumberFormat('en-GB', { style: "currency", currency: 'GBP' }).format( (getValues(`items.${i}.price`) || 0) * (getValues(`items.${i}.quantity`) || 0) )} </StyledField>
完整修正后的渲染代码片段
{fields.map((field, i) => <div key={field.id} tw="grid grid-rows-2 grid-cols-[64px_100px_100px_auto] tablet:flex tablet:flex-row gap-x-6 gap-x-4 gap-y-4 mb-4"> <div tw="flex flex-col col-span-4 tablet:w-[220px]"> <label htmlFor="itemName" tw="text-secondary">Item Name</label> <StyledInput type="text" id="itemName" {...register(`items.${i}.name`, { required: "Can't be empty" })} /> <Error>{errors?.['items']?.[i]?.['name']?.['message']}</Error> </div> <div tw="flex flex-col row-start-2 col-start-1 tablet:w-12"> <label htmlFor="itemsQuantity" tw="text-secondary">Qty.</label> <StyledInput type="text" id="itemsQuantity" {...register(`items.${i}.quantity`, { valueAsNumber: true, required: "Can't be empty" })} /> <Error>{errors?.['items']?.[i]?.['quantity']?.['message']}</Error> </div> <div tw="flex flex-col row-start-2 col-start-2 tablet:w-20"> <label htmlFor="itemPrice" tw="text-secondary">Price</label> <StyledInput type="text" id="itemPrice" {...register(`items.${i}.price`, { valueAsNumber: true, required: "Can't be empty" })} /> <Error>{errors?.['items']?.[i]?.['price']?.['message']}</Error> </div> <div tw="flex flex-col row-start-2 col-start-3 tablet:w-20"> <label htmlFor="itemPrice" tw="text-secondary">Total</label> <StyledField> {Intl.NumberFormat('en-GB', { style: "currency", currency: 'GBP' }).format( (items?.[i]?.price || 0) * (items?.[i]?.quantity || 0) )} </StyledField> </div> <div onClick={() => remove(i)} tw="flex flex-col pt-4 row-start-2 col-start-4 tablet:w-auto place-content-center cursor-pointer"> <Image src="/assets/icon-delete.svg" width={12} height={16} alt="recyclingBin" /> </div> </div> )}
内容的提问来源于stack exchange,提问作者John
相关产品推荐
相关产品推荐

