简体   繁体   English

支持在使用 React Hook 表单验证时更改另一个字段值的回调

[英]Support callback for changing another field value when using React Hook Form validation

TL;DR长话短说

This works: https://codesandbox.io/s/stoic-beaver-ucydi这有效: https://codesandbox.io/s/stoic-beaver-ucydi

After refactor with React Hook Form this does not work: https://codesandbox.io/s/objective-cloud-bkunr?file=/src/ControlledTextField.tsx使用 React Hook Form 重构后,这不起作用: https://codesandbox.io/s/objective-cloud-bkunr?file=/src/ControlledTextField.tsx


Long story很长的故事

Without React Hook Form (works OK)没有 React Hook Form(工作正常)

I've recently built a stateful React form using Fluent UI and wrapped fields in custom components.我最近使用Fluent UI和自定义组件中的包装字段构建了一个有状态的 React 表单。

I've included a feature where the value in Site URL field is generated as you type in Site Title field (it simply copies the field value and removes characters invalid for a URL in my case).我已经包含了一个功能,当您在网站标题字段中键入时,会生成网站 URL 字段中的值(在我的例子中,它只是复制字段值并删除对 URL 无效的字符)。

The (simplified) code was working nicely and looked like this: (简化的)代码运行良好,看起来像这样:

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 component: 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 component: 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}
    />
  );
}

With React Hook Form (not working properly)使用 React Hook Form(无法正常工作)

Now I'm trying to refactor my form using React Hook Form and Yup validation schema.现在我正在尝试使用 React Hook Form 和 Yup 验证模式重构我的表单。

I've wrapped Fluent UI TextField component with React Hook Form Controller component and its render property:我用 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}
        />
      )}
    />
  );
};

I've replaced the code of SiteTitleField and SiteUrlField accordingly and added a simple Yup validation schema:我相应地替换了 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)
});

I've wrapped the form with <form> tag and changed the field properties accordingly:我用<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>

Regarding the state I left only the things needed for value copying:关于 state 我只留下了值复制需要的东西:

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

The Problem问题

I cannot make React Hook Form validation and my value copying feature work at the same time.我无法同时进行 React Hook 表单验证和我的值复制功能。

Either the validation works great but the user cannot edit the Site URL field, when using this code:使用此代码时,验证效果很好但用户无法编辑 Site URL 字段:

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

or copying and editing field values works great but even with the values entered the validation says that both fields are empty (required), when using this code:或复制和编辑字段值效果很好,但即使输入了值,验证也会说两个字段都是空的(必填),当使用此代码时:

onChange={onChangeCallback}
value={refValue}

Ok, I figured that out.好的,我明白了。

Instead of using state to update the field value而不是使用 state 来更新字段值

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

I should be using useForm 's setValue :我应该使用useFormsetValue

  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
    });
  }

and the value in ControlledTextField should be resolved simply as:并且ControlledTextField中的value应该简单地解析为:

value={value}

Working solution: https://codesandbox.io/s/focused-montalcini-ehbp3?file=/src/App.tsx工作解决方案: https://codesandbox.io/s/focused-montalcini-ehbp3?file=/src/App.tsx

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM