支持在使用 React Hook 表单验证时更改另一个字段值的回调
Support callback for changing another field value when using React Hook Form validation
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 表单(工作正常)
我最近使用 Fluent UI 构建了一个有状态的 React 表单,并在自定义组件中包装了字段。
我添加了一项功能,当您在网站标题字段中键入时,会生成网站 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 Form(无法正常工作)
现在我正在尝试使用 React Hook Form 和 Yup 验证模式重构我的表单。
我已经用 React Hook Form Controller 组件及其渲染包装了 Fluent UI TextField 组件 属性:
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 表单验证和我的值复制功能。
验证工作正常但用户无法编辑站点 URL 字段,使用此代码时:
onChange={(event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) => {onChange(refValue); onChangeCallback && onChangeCallback(event);}}
value={refValue}
或复制和编辑字段值效果很好,但即使输入了值,验证也表明两个字段都是空的(必填),使用此代码时:
onChange={onChangeCallback}
value={refValue}
好的,我明白了。
而不是使用状态来更新字段值
const [siteUrl, setsiteUrl] = useState();
function handleTitleChange(e) {
setsiteUrl(e.target.value.replace(/[^A-Za-z0-9_-]/g, ""));
}
我应该使用 useForm
的 setValue
:
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
});
}
和 ControlledTextField
中的 value
应该简单地解析为:
value={value}
工作解决方案:
https://codesandbox.io/s/focused-montalcini-ehbp3?file=/src/App.tsx
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 表单(工作正常)
我最近使用 Fluent UI 构建了一个有状态的 React 表单,并在自定义组件中包装了字段。
我添加了一项功能,当您在网站标题字段中键入时,会生成网站 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 Form(无法正常工作)
现在我正在尝试使用 React Hook Form 和 Yup 验证模式重构我的表单。
我已经用 React Hook Form Controller 组件及其渲染包装了 Fluent UI TextField 组件 属性:
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 表单验证和我的值复制功能。
验证工作正常但用户无法编辑站点 URL 字段,使用此代码时:
onChange={(event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) => {onChange(refValue); onChangeCallback && onChangeCallback(event);}}
value={refValue}
或复制和编辑字段值效果很好,但即使输入了值,验证也表明两个字段都是空的(必填),使用此代码时:
onChange={onChangeCallback}
value={refValue}
好的,我明白了。
而不是使用状态来更新字段值
const [siteUrl, setsiteUrl] = useState();
function handleTitleChange(e) {
setsiteUrl(e.target.value.replace(/[^A-Za-z0-9_-]/g, ""));
}
我应该使用 useForm
的 setValue
:
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
});
}
和 ControlledTextField
中的 value
应该简单地解析为:
value={value}
工作解决方案: https://codesandbox.io/s/focused-montalcini-ehbp3?file=/src/App.tsx