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

TypeScript React Hook Form选择框组件提交失败求助

问题描述

在TypeScript React中开发了三个联动下拉框组件,使用React Hook Form提交表单时,三个选择框的字段值为空,无法提交对应数据,但表单其余部分正常。相关代码如下:

TypeScript组件代码

type CatProdPickerProps = {
  name: string,
  control: any
}
export const CategoryProductPicker = ({name, control}: CatProdPickerProps) => {

  const [selectedCat, setSelectedCat] = useState('');
  const [selectedProd, setSelectedProd] = useState([]);
  const [selectedColor, setSelectedColor] = useState<any[]>([]);

  const { data, isError, isLoading } = useQuery({
    queryKey: ['orders'],
    queryFn: () => fetch('http://localhost:8080/portal').then((res) => {
      if (!res.ok) {
    throw new Error(`Network response was not ok, status ${res.status}`);
      }
      return res.json();
    })
  })
  if (isLoading) {
    return <div>Loading...</div>;

  }
  if (isError) {
    return (
      <div>
    <h1>Error Occured</h1>
    <p>Please try again later</p>
      </div>
    )
  }

  const handleCategoryChange = (categoryName: string) => {
    setSelectedCat(categoryName);

    // Find the selected category in data and get its product names
    const selectedCategoryData = data?.find(
      (item: any) => item.categoryName === categoryName);

    if (selectedCategoryData) {
      // Extract product names as an array of strings
      const productNames = selectedCategoryData.productName.map(
    (p: { productName: string }) => p.productName
      );
      setSelectedProd(productNames);
    } else {
      setSelectedProd([]); // Reset if no matching category
    }

    if (selectedCategoryData) {
      // Extract color names as an array of strings
      const colorNames = selectedCategoryData.colorName.map(
    (p: { colorName: string }) => p.colorName
      );
      setSelectedColor(colorNames);
    } else {
      setSelectedColor([]); // Reset if no matching color
    }
  };

  return (
    <div>
      <Controller
    name={name} control={control} render={({ field: { onChange, value } }) => (
      <><select className="select" name="categoryName" onChange={(e) => handleCategoryChange(e.target.value)}>
        <option value={0}>Select Category</option>
        {data?.map((cat: any) => (
          <option key={cat.ordId} value={cat.categoryName}>{cat.categoryName}</option>
        ))}
      </select>
      <select className="select" name="productName" >
          <option value={0}>Select Product</option>
          {selectedProd.map((product, index) => (
            <option key={index} value={product}>
              {product}
            </option>
          ))}
        </select>
        <select className="select" name="colorName" >
          <option value={0}>Select Colors</option>
          {selectedColor.map((color, index) => (
            <option key={index} value={color}>
              {color}
            </option>
          ))}
        </select></>
    )}
      />
    </div>
  );
}

React Hook Form表单代码

export const CategoryAdd = () => {
  const {
    register,
    handleSubmit,
    formState: { isSubmitting },
    reset,
    control,
  } = useForm({
    defaultValues: {
    }
  });

  const onSubmit = async (data: FieldValues) => {
    await fetch("http://localhost:8080/portal/createOrder", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(data)
    })
  };

  return (
    <Container maxWidth="lg">
      <Box marginTop={10} sx={{ height: '20vh' }}>
    <form onSubmit={handleSubmit(onSubmit)} className="flex flex-col gap-y-2">
      
          <CategoryProductPicker control={control} name="" />

      <Stack spacing={10} direction="row">
        <Button variant="contained"
          disabled={isSubmitting}
          type="submit"
          className="bg-blue-500 disabled:bg-gray-500 py-2 rounded"
        >
          Submit
        </Button>
      </Stack>
    </form>
      </Box>
    </Container >
  );
}

问题排查与解决方案

核心问题分析

  • Controller的name属性为空:调用CategoryProductPicker时传入的name是空字符串,React Hook Form无法识别该字段,无法收集数据。
  • 未同步表单状态:下拉框变化仅更新了组件内部useState,未调用React Hook Form的onChange方法,导致表单无法捕获值变化。
  • 单个Controller绑定多字段:用一个Controller包裹三个下拉框不符合React Hook Form逻辑,每个表单字段需独立绑定。

具体修改步骤

1. 传入有效的字段名前缀

修改CategoryAdd中的组件调用,传入有意义的name,用于后续嵌套字段命名:

<CategoryProductPicker control={control} name="order" />

2. 重构CategoryProductPicker,使用多Controller管理字段

每个下拉框对应独立的Controller,联动更新选项同时同步表单状态:

import { useController, Control } from 'react-hook-form';
import { useState, useEffect } from 'react';
import { useQuery } from '@tanstack/react-query';

type OrderFormValues = {
  categoryName: string;
  productName: string;
  colorName: string;
};

type CatProdPickerProps = {
  name: string;
  control: Control<{ [key: string]: OrderFormValues }>;
};

export const CategoryProductPicker = ({ name, control }: CatProdPickerProps) => {
  const [productOptions, setProductOptions] = useState<string[]>([]);
  const [colorOptions, setColorOptions] = useState<string[]>([]);

  const { data, isError, isLoading } = useQuery({
    queryKey: ['orders'],
    queryFn: () =>
      fetch('http://localhost:8080/portal').then((res) => {
        if (!res.ok) {
          throw new Error(`Network response was not ok, status ${res.status}`);
        }
        return res.json();
      }),
  });

  if (isLoading) return <div>Loading...</div>;
  if (isError) return (
    <div>
      <h1>Error Occured</h1>
      <p>Please try again later</p>
    </div>
  );

  // 监听分类字段变化,联动更新产品和颜色选项
  const { field: categoryField } = useController({
    control,
    name: `${name}.categoryName`,
    defaultValue: '',
  });

  useEffect(() => {
    if (!categoryField.value) {
      setProductOptions([]);
      setColorOptions([]);
      return;
    }

    const selectedCategoryData = data?.find(
      (item: any) => item.categoryName === categoryField.value
    );

    if (selectedCategoryData) {
      const productNames = selectedCategoryData.productName.map(
        (p: { productName: string }) => p.productName
      );
      const colorNames = selectedCategoryData.colorName.map(
        (p: { colorName: string }) => p.colorName
      );
      setProductOptions(productNames);
      setColorOptions(colorNames);
    } else {
      setProductOptions([]);
      setColorOptions([]);
    }
  }, [categoryField.value, data]);

  return (
    <div className="flex flex-col gap-y-2">
      {/* 分类选择框 */}
      <select
        className="select"
        value={categoryField.value}
        onChange={(e) => categoryField.onChange(e.target.value)}
      >
        <option value="">Select Category</option>
        {data?.map((cat: any) => (
          <option key={cat.ordId} value={cat.categoryName}>
            {cat.categoryName}
          </option>
        ))}
      </select>

      {/* 产品选择框 */}
      <Controller
        control={control}
        name={`${name}.productName`}
        defaultValue=""
        render={({ field: { onChange, value } }) => (
          <select
            className="select"
            value={value}
            onChange={(e) => onChange(e.target.value)}
            disabled={productOptions.length === 0}
          >
            <option value="">Select Product</option>
            {productOptions.map((product, index) => (
              <option key={index} value={product}>
                {product}
              </option>
            ))}
          </select>
        )}
      />

      {/* 颜色选择框 */}
      <Controller
        control={control}
        name={`${name}.colorName`}
        defaultValue=""
        render={({ field: { onChange, value } }) => (
          <select
            className="select"
            value={value}
            onChange={(e) => onChange(e.target.value)}
            disabled={colorOptions.length === 0}
          >
            <option value="">Select Colors</option>
            {colorOptions.map((color, index) => (
              <option key={index} value={color}>
                {color}
              </option>
            ))}
          </select>
        )}
      />
    </div>
  );
};

3. 添加表单默认值

在CategoryAdd的useForm中配置对应字段的默认值:

const {
  register,
  handleSubmit,
  formState: { isSubmitting },
  reset,
  control,
} = useForm({
  defaultValues: {
    order: {
      categoryName: '',
      productName: '',
      colorName: ''
    }
  }
});

关键修改说明

  • 使用useController获取分类字段状态,通过useEffect监听分类变化联动更新产品和颜色选项。
  • 每个下拉框通过Controller绑定到表单状态,确保选择变化同步到表单数据。
  • 将空选项值改为空字符串"",避免提交无效数值类型数据。
  • 添加disabled属性,无选项时禁用产品和颜色选择框,优化用户体验。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 11:54:54