@toster1500

Возможно ли зарегистрировать свой кастомный компонент в useForm?

Подскажите, пожалуйста, возможно ли сделать мой собственный компонент Select таким образом, чтобы его можно было зарегистрировать в useForm так же, как обычные элементы HTML, например ?

Вот пример кода в codesanbox

import "./styles.css";
import { Controller, useForm } from "react-hook-form";
import Select, { ISelectOption } from "./Select";

const people: ISelectOption[] = [
  { id: 1, value: "1", label: "Durward Reynolds" },
  { id: 2, value: "2", label: "Kenton Towne" },
  { id: 3, value: "3", label: "Therese Wunsch" },
  { id: 4, value: "4", label: "Benedict Kessler" },
  { id: 5, value: "5", label: "Benedict Kessler" },
  { id: 6, value: "6", label: "Benedict Kessler" },
  { id: 7, value: "7", label: "Benedict Kessler" },
  { id: 8, value: "8", label: "Katelyn Rohan" }
];

export default function App() {
  const { control, handleSubmit } = useForm();

  const onSubmit = (data) => {
    console.log(data.Select);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Select
        options={people}
        name="Select"
        defaultText="Select"
        required
        placeholder="Select placeholder"
        control={control}
        Controller={Controller}
        defaultValue={people[2].value}
      />
      <button type="submit">Submit</button>
    </form>
  );
}


import { ChangeEvent, FC, useEffect, useMemo, useRef, useState } from "react";
import { Listbox } from "@headlessui/react";
import { Control, ControllerProps, FieldValues } from "react-hook-form";

export interface ISelectOption {
  id: number | string;
  value: string | number;
  label: string;
}

interface ISelect {
  name: string;
  classes?: string;
  disabled?: boolean;
  required?: boolean;
  defaultText: string;
  placeholder?: string;
  options: ISelectOption[];
  control: Control<FieldValues, any>;
  defaultValue?: ISelectOption["value"];
  Controller: FC<ControllerProps<FieldValues, string>>;
}

const Select: FC<ISelect> = ({
  control,
  Controller,
  required,
  name,
  defaultText,
  disabled,
  classes,
  options,
  placeholder,
  defaultValue
}) => {
  const selectRef = useRef<HTMLDivElement>(null);

  const defaultOption = useMemo(
    () =>
      defaultValue
        ? options.find((option) => option.value === defaultValue)
        : options[0],
    [defaultValue, options]
  );

  const [isOpen, setIsOpen] = useState<boolean>(false);
  const [selectedOptionValue, setSelectedOptionValue] = useState<
    ISelectOption["value"] | null
  >(defaultValue ?? "");
  const [selectedOption, setSelectedOption] = useState<ISelectOption | null>(
    defaultOption
  );
  const [searchValue, setSearchValue] = useState("");

  const filteredOptions = useMemo(
    () =>
      options.filter(({ label }) =>
        label.toLowerCase().includes(searchValue.toLowerCase())
      ),
    [options, searchValue]
  );

  const shouldShowSearch = options.length > 5;

  useEffect(() => {
    const handleClickOutside = (event: MouseEvent) => {
      if (
        selectRef.current &&
        !selectRef.current.contains(event.target as Node)
      ) {
        setIsOpen(false);
      }
    };
    document.addEventListener("mousedown", handleClickOutside);
    return () => document.removeEventListener("mousedown", handleClickOutside);
  }, [selectRef]);

  const onSelectOption = (option: ISelectOption) => {
    setIsOpen(false);
    setSelectedOptionValue(option.value);
    setSelectedOption(option);
  };

  return (
    <Controller
      name={name}
      control={control}
      rules={{ required: !defaultValue && required }}
      defaultValue={defaultValue ? defaultOption : null}
      render={({
        field: { onChange, value },
        fieldState: { error },
        formState
      }) => (
        <div
          className={`select form-label ${isOpen ? "--open" : ""} ${
            classes ?? ""
          } ${disabled ? "--disabled" : ""} ${error ? "--error" : ""}`}
          ref={selectRef}
        >
          {placeholder && (
            <span className="form-label__placeholder">{placeholder}:</span>
          )}
          <Listbox value={value} onChange={(value) => onChange(value)}>
            <Listbox.Button onClick={() => setIsOpen(!isOpen)}>
              <p>{selectedOptionValue ? selectedOption?.label : defaultText}</p>
              <i className="icon icon-arrow"></i>
            </Listbox.Button>
            <Listbox.Options>
              {shouldShowSearch && (
                <label className="select__search">
                  <input
                    value={searchValue}
                    placeholder="Search..."
                    onChange={(event: ChangeEvent<HTMLInputElement>) =>
                      setSearchValue(event.target.value)
                    }
                  />
                  <i className="icon icon-search" />
                </label>
              )}
              {filteredOptions.length > 0 ? (
                filteredOptions.map(({ id, value, label }: ISelectOption) => (
                  <Listbox.Option
                    key={id}
                    value={{ id, value, label }}
                    onClick={() => onSelectOption({ id, value, label })}
                    className={
                      selectedOptionValue === value ? "--selected" : ""
                    }
                  >
                    {label}
                  </Listbox.Option>
                ))
              ) : (
                <li className="select__not-results">No results found</li>
              )}
            </Listbox.Options>
          </Listbox>
        </div>
      )}
    />
  );
};

export default Select;
  • Вопрос задан
  • 65 просмотров
Пригласить эксперта
Ответы на вопрос 1
miraage
@miraage
Старый прогер
Выглядит, как взять register из useForm, и прокинуть его через контекст всем дочерним компонентам, чтобы они в свою очередь вызывали register для нужных компонентов.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы