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

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>
    );
};

问题原因

  1. React Hook Form中field.value是只读状态,直接赋值不会触发表单状态更新,必须通过官方提供的方法修改值。
  2. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 15:54:57