Поддержка обратного вызова для изменения другого значения поля при использовании проверки формы React Hook

TL;DR

Это работает: https://codesandbox.io/s/stoic-beaver-ucydi

После рефакторинга с помощью React Hook Form это не работает: https://codesandbox.io/s/objective-cloud-bkunr?file=/src/ControlledTextField.tsx


Длинная история

Без формы React Hook (работает нормально)

Недавно я создал форму React с отслеживанием состояния, используя Fluent UI и обернутые поля в пользовательские компоненты.

Я включил функцию, при которой значение в поле «URL-адрес сайта» создается при вводе в поле «Заголовок сайта» (в моем случае оно просто копирует значение поля и удаляет символы, недопустимые для URL-адреса).

(Упрощенный) код работал нормально и выглядел так:

import * as React from 'react';
import {useState} from 'react';
import { PrimaryButton } from 'office-ui-fabric-react';
import SiteTitleField from '../../../common/formFields/SiteTitleField';
import SiteUrlField from '../../../common/formFields/SiteUrlField';

export default function MyForm(props) {

  const urlPrefix: string = "https://" + window.location.hostname + "/sites/";

  const [siteTitle, setSiteTitle] = useState();
  const [titleErrorMessage, setTitleErrorMessage] = useState('');

  const [siteUrl, setsiteUrl] = useState();
  const [urlErrorMessage, setUrlErrorMessage] = useState('');

  function handleTitleChange(e) {
    if (e.target.value.length) {
      setTitleErrorMessage('');
    } else {
      setTitleErrorMessage('This field is required.');
    }
    setSiteTitle(e.target.value);
    setsiteUrl(e.target.value.replace(/[^A-Za-z0-9_-]/g, ""));
  }
  
  function handleUrlChange(e) {
    if (e.target.value.length) {
      setUrlErrorMessage('');
    } else {
      setUrlErrorMessage('This field is required.');
    }
    setsiteUrl(e.target.value);
  }
  
  function handleButtonClick(e) {
    // call to API
  }

  return (
    <SiteTitleField
      siteTitle={siteTitle}
      titleErrorMessage={titleErrorMessage}
      handleTitleChange={handleTitleChange}
    />

    <SiteUrlField
      siteUrl={siteUrl}
      urlErrorMessage={urlErrorMessage}
      urlPrefix={urlPrefix}
      handleUrlChange={handleUrlChange}
    />

    <PrimaryButton 
      text="Create a Request" 
      onClick={handleButtonClick}
    />
  );
}

Компонент SiteTitleField:

import * as React from 'react';
import { TextField } from 'office-ui-fabric-react/lib/TextField';

export default function SiteTitleField(props) {
  return (
    <TextField 
      value={props.siteTitle}
      required 
      aria-required="true"
      errorMessage={props.titleErrorMessage}
      label="Site Title" 
      placeholder="Set the title of the site"
      onChange={props.handleTitleChange}
    />
  );
}

Компонент SiteUrlField:

import * as React from 'react';
import { TextField } from 'office-ui-fabric-react/lib/TextField';

export default function SiteUrlField(props) {
  return (
    <TextField
      value={props.siteUrl}
      required
      aria-required="true"
      errorMessage={props.urlErrorMessage}
      label="Site URL"
      prefix={props.urlPrefix}
      placeholder="Set site URL alias"
      onChange={props.handleUrlChange}
    />
  );
}

С формой React Hook (не работает должным образом)

Теперь я пытаюсь реорганизовать свою форму, используя React Hook Form и схему проверки Yup.

Я обернул компонент Fluent UI TextField компонентом React Hook Form Controller и его свойством рендеринга:

import * as React from 'react';
import { Control, Controller, FieldErrors } from "react-hook-form";
import { TextField } from 'office-ui-fabric-react';

export interface IControlledTextFieldProps {
    control: Control<any>;
    name: string;
    errors: FieldErrors<any>;
    label?: string;
    prefix?: string;
    placeholder?: string;

    onChangeCallback?: (...event: any[]) => void;
    refValue?: string;
}
  
export const ControlledTextField: React.FC<IControlledTextFieldProps> = ({
  control,
  name,
  errors,
  label,
  prefix, 
  placeholder,

  onChangeCallback,
  refValue,

}) => {
  return (
    <Controller
      name={name}
      control={control}
      disabled={disabled}
      render={({ onChange, onBlur, value, name: fieldName }) => (
        <TextField

          onChange={(event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) => {onChange(refValue); onChangeCallback && onChangeCallback(event);}}
          value={refValue}

          onBlur={onBlur}
          name={fieldName}
          errorMessage={errors[fieldName] && errors[fieldName].message}
          label={label}
          prefix={prefix}
          placeholder={placeholder}
        />
      )}
    />
  );
};

Я заменил код SiteTitleField и SiteUrlField соответственно и добавил простую схему проверки Yup:

const schema = yup.object().shape({
  siteTitle: yup.string().required("Site Title needs to be provided."),
  siteUrl: yup.string().required("Site URL needs to be provided."),
});

const { handleSubmit, errors, control } = useForm<Inputs>({
  resolver: yupResolver(schema)
});

Я обернул форму тегом <form> и соответственно изменил свойства поля:

<form onSubmit={handleSubmit(handleButtonClick)}>
    <SiteTitleField
      name="siteTitle"
      control={control}
      errors={errors}
      handleTitleChange={handleTitleChange}
    />
    
    <SiteUrlField
      name="siteUrl"
      control={control}
      errors={errors}
      siteUrl={siteUrl}
      urlPrefix={urlPrefix}
    />
        
    <PrimaryButton 
      text="Create a Request" 
      type="submit"
    />
</form>

Что касается состояния, я оставил только то, что нужно для копирования значений:

  const [siteUrl, setsiteUrl] = useState();
  
  function handleTitleChange(e) {
    setsiteUrl(e.target.value.replace(/[^A-Za-z0-9_-]/g, ""));
  }

Эта проблема

Я не могу сделать так, чтобы проверка формы React Hook Form и моя функция копирования значений работали одновременно.

Либо проверка работает отлично, но пользователь не может редактировать поле URL-адреса сайта при использовании этого кода:

onChange={(event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) => {onChange(refValue); onChangeCallback && onChangeCallback(event);}}
value={refValue}

или копирование и редактирование значений полей отлично работает, но даже с введенными значениями проверка говорит, что оба поля пусты (обязательны) при использовании этого кода:

onChange={onChangeCallback}
value={refValue}

person Mariusz Ignatowicz    schedule 02.12.2020    source источник


Ответы (1)


Хорошо, я понял это.

Вместо использования состояния для обновления значения поля

  const [siteUrl, setsiteUrl] = useState();
  
  function handleTitleChange(e) {
    setsiteUrl(e.target.value.replace(/[^A-Za-z0-9_-]/g, ""));
  }

Я должен использовать setValue useForm:

  const { 
    handleSubmit, 
    errors, 
    control, 
    setValue // added
  } = 
  useForm<Inputs>({
    resolver: yupResolver(schema)
  });

  function handleTitleChange(e) {
    // changed:
    setValue("siteUrl", e.target.value.replace(/[^A-Za-z0-9_-]/g, ""), {
      shouldValidate: true
    });
  }

и value в ControlledTextField следует разрешить просто как:

value={value}

Рабочее решение: https://codesandbox.io/s/focused-montalcini-ehbp3?file=/src/App.tsx

person Mariusz Ignatowicz    schedule 02.12.2020