首页 > 解决方案 > 如何将自定义下拉组件中的值传递给 react-hook-form?

问题描述

react-hook-form在这个 Gatsby 项目的表单中使用验证,但我的下拉组件不是<select>标签,而是使用 div 和无序列表制作的自定义组件。这完全是一个设计选择,因为我们需要它非常定制。这是组件

// Dropdown.js
import React, { useState } from "react";
import "../../sass/components/forms/dropdown.sass"


export function Dropdown({ preambulo, name, options, placeholder, value}) {
  const [isOpen, setIsOpen] = useState(false);
  const [selectedOption, setSelectedOption] = useState(null);
    
  const toggling = () => setIsOpen(!isOpen);

  const onOptionClicked = value => () => {
    setSelectedOption(value);
    setIsOpen(false);
  };

  return (
    <React.Fragment>
      <span>{preambulo}</span>
      <div 
        className={`
          dropdownHeader 
          ${isOpen === true ? 'open' : 'closed'} 
          `} 
          onClick={toggling}
          >
        {selectedOption || placeholder}
      </div>
      {isOpen && (
        <div 
          className="dropdownListContainer">
            <ul 
              className="dropdownList"
              name={name}
              id={name}
              value={value}
              >
              {options.map((option, i) => (
                <li 
                  className={`dropdownListItem item-${i}`} 
                  key={i} 
                  onClick={onOptionClicked(option)}>
                  {option}
                </li>
              ))}
              <hr />
            </ul>
        </div>
        )}
    </React.Fragment>
  );
}

稍微解释一下,道具是从渲染组件<Controller>的地方传递的。react-hook-form组件本身具有处理列表的状态,当用户单击标题时将打开该列表。很简单:const toggling = () => setIsOpen(!isOpen);做。并且选项的值是从传递给父元素的 const 映射的,然后一旦选择了某些东西,它就会像 a<select>一样发生在标题中。

显示下拉菜单行为的 Gif

这是表单的代码(Edit2):

// contato.js
import React from "react";
import { useForm, Controller } from "react-hook-form";

//Form Components
import { Dropdown } from "../../components/forms/Dropdown"
import { Input, TextArea } from "../../components/forms/Input"

const ContatoFull = ({ className }) => {
 
  const cargos = [
    {//** values **/}
  ]

  const estados = [
    "Acre",
    "Alagoas",
    "Amazonas",
    "Amapá",
    "Bahia",
    "Ceará",
    "Distrito Federal",
    "Espírito Santo",
    "Goiás",
    "Maranhão",
    "Minas Gerais",
    "Mato Grosso do Sul",
    "Mato Grosso",
    "Pará",
    "Paraíba",
    "Pernambuco",
    "Piauí",
    "Paraná",
    "Rio de Janeiro",
    "Rio Grande do Norte",
    "Rondônia",
    "Roraima",
    "Rio Grande do Sul",
    "Santa Catarina",
    "Sergipe",
    "São Paulo",
    "Tocantins"
  ]
  const methods = useForm();
  const { handleSubmit, control, formState, errors } = methods;
  
  const onSubmit = data => {
    alert(JSON.stringify(data));
  };
  
  console.log(formState);

  return (
    <form 
      method="post"
      className={`${className !== 0 ? className : ''}`} 
      onSubmit={handleSubmit(onSubmit)}>
      
      <div
        className={`dropdownContainer cargo`}>
        <Controller
          name="cargo"
          control={control}
          render={({ onClick, name, value }) =>
          <Dropdown 
            onClick={e => onClick(e.target.value)}
            value={value}
            preambulo="Eu sou" 
            placeholder="Placeholder" 
            name={name}
            options={cargos}
            />
          }
        />
      </div>

      <div
        className={`dropdownContainer estado`}>
        <Controller
          name="estado"
          control={control}
          render={({ onClick, name, value }) =>
          <Dropdown 
            onClick={e => onClick(e.target.value)}
            value={value}
            name={name}
            preambulo="Estou em/no" 
            placeholder="acre" 
            options={estados}
              />
          }
        />
      </div>

      <div 
        className={`inputContainer empresa`}>
        <Controller 
          name="empresa" 
          type="text "
          control={control}
          defaultValue=""
          rules={{ required: true }}
          render={({ onChange, value, name, type, label }) =>
          <Input 
            onChange={e => onChange(e.target.value)}
            value={value}
            label="Nome da minha empresa" 
            type="text" />
          }
        />
        {errors.empresa && <span className="erro requerido">Campo obrigatório</span>}
        
        
      </div>

      {//** more input components //*^}

      <button 
        type="submit" 
        className="simpleButton primary submit button">Enviar</button>
    </form>
  )
}

export default ContatoFull

现在,我们要处理两个问题:1)如何将值传递给表单,因为我们没有使用标准标签进行选择;和 2) 即使切换适用于单击标题或从列表中选择一个选项,单击外部也不会关闭它,如下所示。

显示问题 #2 的 Gif

我不确定如何解决这些问题。我相信问题 #1 需要一个钩子来获取子组件选择的值,但不知道这个钩子是什么,也不知道如何将它与 react-hook-form 一起使用。对于问题 #2,也许一个捕获外部点击的函数会切换回打开的列表。事情是我已经设法定位到标题而不是它的外部,或者“非标题”部分,如果那是一件事的话。

编辑:设法使用此库检测外部点击:react-outside-click-handler。一个简单问题的绝佳解决方案。

编辑2:阅读@dshung1997 的评论我意识到我没有在此处粘贴父组件的代码。只是纠正这个。谢谢!

标签: javascriptreactjsreact-hook-form

解决方案


如果您需要将 selectedOption 获取到父级,首先您假设父级是事实的来源,这就是您必须为 Dropdown 提供函数的原因。

假设我们使用一个选择标签,它会是这样的

export const Parent = () => {
  const [value, setValue] = useState("1");
  return (
    <select value={value} onChange={(e) => setValue(e.target.value)}>
      <option value="1">1</option>
      <option value="2">2</option>
      <option value="3">3</option>
    </select>
  );
};

所以我们需要一个值的变量(value)和一个更新该值的函数(setValue)。

“目标是在父组件上获取这个 selectedOption 值,以便表单在提交时发布它?”。只有父母知道何时提交表单。下拉列表不应该也不需要确定。目标是告诉父级在更改值后立即更新值,而不是在提交时将值提供给父级。

因此,您需要为 Dropdown 提供一个函数来更新值。


推荐阅读