ShadCN表单手动更新输入框值问题求助
解决ShadCN输入框点击序列号建议无法更新值的问题
我正在构建一个展示序列号建议的表单,通过API获取数据的功能正常,能正确获取并展示所有序列号建议,但点击建议项时,无法更新ShadCN输入框中存储的值,相关逻辑在ProductInput组件内。尝试直接设置FormField的field.value = serialNo,输入文本框并未更新,相关代码如下:
import { FormControl, FormField, FormItem, FormLabel, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { useFieldArray } from "react-hook-form"; import { useFormContextServiceJob } from "@/app/_components/service-job-form/serviceJobFormProvider"; import { useEffect, useState } from "react"; import { componentsFetcher, productsFetcher } from "./itemsFetcher"; import { ComponentProps, ProductProps } from "@/next-auth"; import { Session } from "next-auth"; import { Components } from "./components"; const ProductInput = ({ user, prodIndex, id, keyVal, remove, }: { user: Session; prodIndex: number; id: string; keyVal: string; remove: any; }) => { const { control, watch, register } = useFormContextServiceJob(); const { fields, update } = useFieldArray({ name: `products`, control, }); const currData = watch(); const [products, setProducts] = useState<ProductProps[] | null>(null); useEffect(() => { setProducts(null); }, []); let debounceTime: ReturnType<typeof setTimeout>; const debounce = (fetchSuggestions: any, delay: number) => { return (...args: any[]) => { clearTimeout(debounceTime); debounceTime = setTimeout(() => fetchSuggestions(...args), delay); }; }; const handleInputChange = debounce((query: string) => { fetchSuggestions(query); }, 300); const fetchSuggestions = async (query: string) => { try { const data = await productsFetcher( user.user.access_token as string, query ); console.log("Data of query: ", query, " is: "); console.log(data); if (data.length) setProducts(() => data); else throw new Error(""); } catch (e: any) { setProducts(null); } }; const setSelectedProduct = (val: { productId: string; serialNo: string }) => { const { serialNo } = val; update(prodIndex, { serialNo, components: [], }); setProducts(null); setTimeout(() => { console.log("Current data is: ", currData); console.log("NEW state: ", fields); }, 2000); }; console.log("CURR DATA IS HEHEHE: ", currData); console.log("CURR FILED IS HEHEHE: ", fields); useEffect(() => { if ( !( products && products.length === 1 && currData.products[prodIndex].serialNo === products[0].serialNo ) ) handleInputChange(currData.products[prodIndex].serialNo); else setProducts(() => null); }, [currData?.products[prodIndex]?.serialNo]); return ( <div key={keyVal}> <FormField control={control} name={`products.${prodIndex}.serialNo`} render={({ field, fieldState }) => { console.log("FIELDDDD: ", field); return ( <FormItem> <FormLabel className="bg-green-400 px-3 py-1 rounded-lg"> Product {prodIndex + 1} </FormLabel> <FormControl> <div className="relative"> <Input placeholder="Serial Number" type="text" {...register(`products.${prodIndex}`)} {...field} onChangeCapture={(e) => { console.log(e); }} className="inputfield" /> <Button className="absolute right-0 top-1/2 -translate-y-1/2 scale-95 transition-all duration-150 hover:scale-90 active:scale-75 w-20 sm:w-44" onClick={() => remove(prodIndex)} variant={"destructive"} > <h1 className="text-ellipsis overflow-hidden"> Remove Product </h1> </Button> </div> </FormControl> <div> {products && products.length && products.map((item, ind) => ( <div key={ind} className="w-full py-3 bg-white px-2 text-lg rounded-lg text-ellipsis overflow-hidden text-nowrap cursor-pointer hover:scale-95 active:scale-90 transition-all duration-200" onClick={() => { field.value = item.serialNo; console.log("field: ", field.value); setSelectedProduct(item); }} > {item.serialNo} </div> ))} </div> </FormItem> ); }} /> <Components prodId={currData.products[prodIndex].serialNo} prodIndex={prodIndex} id={id} user={user} /> </div> ); }; export const Products = ({ id, ind, user, }: { id: string; ind: number; user: Session; }) => { const { control } = useFormContextServiceJob(); const { append, fields, remove } = useFieldArray({ name: `products`, control, }); return ( <div> {fields.map((product, prodIndex) => ( <ProductInput keyVal={product.id} user={user} prodIndex={prodIndex} id={id} key={product.id} remove={remove} /> ))} <div> <Button className="scale-95 transition-all duration-150 hover:scale-90 active:scale-75 w-20 sm:w-44 mt-5 bg-orange-400" onClick={() => { append({ serialNo: "", components: [] }); }} > <h1 className="text-ellipsis overflow-hidden whitespace-nowrap"> Add new Product </h1> </Button> </div> </div> ); };
问题原因
- React Hook Form中
field.value是只读状态,直接赋值不会触发表单状态更新,必须通过官方提供的方法修改值。 - Input组件同时使用
register和field属性,存在属性冲突,导致值更新异常。
修复步骤
1. 用field.onChange更新输入框值
点击建议项时,调用field.onChange触发输入框状态更新,替代直接赋值field.value:
onClick={() => { field.onChange(item.serialNo); setSelectedProduct(item); }}
2. 清理Input组件的重复属性
删除Input上的{...register(products.${prodIndex})},FormField的field属性已包含register的所有必要属性,重复使用会导致冲突:
<Input placeholder="Serial Number" type="text" {...field} onChangeCapture={(e) => { console.log(e); }} className="inputfield" />
3. 优化setSelectedProduct逻辑
直接用watch()获取最新表单数据,无需延迟日志:
const setSelectedProduct = (val: { productId: string; serialNo: string }) => { const { serialNo } = val; update(prodIndex, { serialNo, components: [], }); setProducts(null); console.log("Current data is: ", watch()); console.log("NEW state: ", fields); };
内容的提问来源于stack exchange,提问作者Pulkit Raina
相关产品推荐
相关产品推荐

