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}