反应:如何使用formik从父验验证子组件值

发布于 2025-02-12 02:11:48 字数 12142 浏览 0 评论 0原文

我有以下表格,我使用formik验证:

“

这些验证由父级验证架构控制。现在有一个新的要求,将上传到“样式详细信息”框的任何文件都不应具有其名称中的特殊字符(例如 [email  prectioned] 应该无效)。用户可以将文件拖放到此组件中。

什么应该是理想的方法?我有点困惑,因为样式详细信息框不是一个基于简单的输入组件,我不确定如何在文件上载并打印错误“无效的文件名”时如何验证文件名,以防验证失败。

有人可以帮助我吗?

我的表格:

<Formik
      initialValues={latestOrder}
      innerRef={formRef}
      // enableReinitialize={true}
      validationSchema={OrderValidationSchema}
      onSubmit={_handleSubmit}>
      {({ values, isSubmitting, errors, dirty, isValid }) => {
        console.log('checking values BasicOrderForm', values, errors)
        return (
          <Form
            css={css(
              `min-width: 500px; max-width: 100%; margin: 0 auto; position: relative;`,
            )}>
            {onBackButtonClick != null && (
              <LabelledPageTitle
                pageTitle="New Order"
                onBackArrowClick={onBack}
                onCloseArrowClick={null}
              />
            )}

            <Spacer height={15} />
            {showProgressBar && (
              <ProgressBar
                items={['Order Info', 'Details', 'Order Submitted']}
                containerStyle={`max-width: 500px;`}
                currentStepIndex={0}
              />
            )}
            <FormBackground hideFormBackgroundColor={hideFormBackgroundColor}>
              {subheading && <Subheading>{subheading}</Subheading>}

              <FormikLabelledTextInput
                label={OrderForm.formField.orderName.label}
                name={OrderForm.formField.orderName.name}
                disabled={readonly}
                placeholder={OrderForm.formField.orderName.placeholder}
              />

              <Spacer height={15} />
              <FormikLabelledDateInput
                disabled={readonly}
                name={OrderForm.formField.expectedDeliveryDate.name}
                label={OrderForm.formField.expectedDeliveryDate.label}
                placeholder={
                  OrderForm.formField.expectedDeliveryDate.placeholder
                }
                minDate={new Date(new Date().getTime() + 86400000)}
                rightDecorativeComponent={<AiOutlineCalendar />}
              />
              <FormikFileUploadComponent
                disabled={readonly}
                name={OrderForm.formField.orderFilesFe.name}
                label={OrderForm.formField.orderFilesFe.label}
              />
              <Spacer height={15} />
              <FormikLabelledTextArea
                disabled={readonly}
                name={OrderForm.formField.outputRequirement.name}
                label={OrderForm.formField.outputRequirement.label}
                placeholder={OrderForm.formField.outputRequirement.placeholder}
              />
              <Spacer height={15} />
              <div className="row">
                <div className="col-6" css={css(`padding-right: 7.5px;`)}>
                  <FormikLabelledMultiSelect
                    name={OrderForm.formField.year.name}
                    disabled={readonly}
                    label={OrderForm.formField.year.label}
                    options={years}
                    notAllowNull="false"
                    placeholder={OrderForm.formField.year.placeholder}
                  />
                </div>
                <div className="col-6" css={css(`padding-left: 7.5px;`)}>
                  <FormikLabelledMultiSelect
                    name={OrderForm.formField.season.name}
                    disabled={readonly}
                    label={OrderForm.formField.season.label}
                    options={SEASONS}
                    notAllowNull="false"
                    placeholder={OrderForm.formField.season.placeholder}
                  />
                </div>
              </div>
              <Spacer height={15} />
              <FormikLabelledDropdown
                disabled={readonly}
                containerStyle={`max-width: unset;`}
                name={OrderForm.formField.threeDSoftware.name}
                label={OrderForm.formField.threeDSoftware.label}
                placeholder={OrderForm.formField.threeDSoftware.placeholder}
                width="100%"
                notAllowNull="false"
                options={software}
              />
              <Spacer height={15} />
              {orderOrganization == null ||
              BRANDS[orderOrganization] == null ? (
                <ErrorSpan
                  css={css(
                    `line-height: 30px; font-size: 16px; font-weight: 600;`,
                  )}>
                  Error: Invalid organization name ({orderOrganization})
                </ErrorSpan>
              ) : (
                <FormikLabelledDropdown
                  disabled={readonly}
                  containerStyle={`max-width: unset;`}
                  name={OrderForm.formField.brandDivision.name}
                  label={OrderForm.formField.brandDivision.label}
                  placeholder={OrderForm.formField.brandDivision.placeholder}
                  width="100%"
                  notAllowNull="false"
                  options={BRANDS[orderOrganization]}
                />
              )}
              <Spacer height={10} />
            </FormBackground>

            {onNextButtonClick == null ? (
              <></>
            ) : (
              <>
                <Spacer height={15} />
                <PrimaryButton
                  isDisabled={
                    //!(isValid && dirty) ||
                    readonly
                  }
                  type={'submit'}
                  onClick={null}>
                  {buttonText ? buttonText : 'Next'}
                </PrimaryButton>
              </>
            )}
            <AcceptOrderButton order={values} allowClick={isValid} />
          </Form>
        )
      }}
    </Formik>

样式详细信息(FileUploAdcomponent):

const FormikFileUploadComponent: React.FC<IFormikFileUploadComponentProps> = (
  props,
) => {
  const { t, i18n } = useTranslation()
  const fileInputField = useRef(null)
  const [isLoading, setIsLoading] = useState(false)
  const [progress, setProgress] = useState<number>(0)
  const [elapsedTime, setElapsedTime] = useState<number>(0)

  const [field, meta, helpers] = useField(props.name)
  const files = field.value

  const _renderErrorText = () => {
    const error = meta.error
    const touched = meta.touched
    if (error && touched) {
      return error
    } else return null
  }

  const error = props.name ? _renderErrorText() : null

  const onFilesUploadedFn = (files: File[]) => {
    helpers.setValue(files, true)
  }

  const onFileUploadClick = () => {
    let input = document.getElementById('fileUpload' + props.label) as any
    input.value = null
    input.click()
  }

  const onFileUpload = async (e) => {
    let fileList = e?.target?.files
    await uploadFiles(fileList)
  }

  const uploadFiles = async (fileList: File[]) => {
    setIsLoading(true)
    try {
      if (!fileList) {
        setIsLoading(false)
        return
      }

      // Return uploaded files to parent
      let existingFiles = files == null ? [] : files
      let newList = [...existingFiles]

      Array.from(fileList).forEach((newFile, i) => {
        let index = newList.findIndex((t) => t.name == newFile.name)
        if (index > -1) {
          newList[index]['delete'] = false
        } else {
          newList.push(newFile)
        }
      })
      onFilesUploadedFn(newList)

      setIsLoading(false)
    } catch (error) {
      alert(error)
      setIsLoading(false)
    }
  }

  const onDeleteClick = async (e, file: File) => {
    let fileClone = [...files]
    const index = fileClone.findIndex((item) => item.name == file.name)
    if (index > -1) {
      fileClone[index]['delete'] = true
    }
    onFilesUploadedFn(fileClone)
    e.stopPropagation()
  }

  const clickBackgroundContainer = (e) => {
    onFileUploadClick()
  }

  const clickThumbnail = (e, file: File) => {
    e.stopPropagation()
    e.preventDefault()
    if (file['filePath']) {
      downloadFileFromPath(file['filePath'])
    } else {
      window.open(URL.createObjectURL(file))
    }
  }

  const handleDragOver = (e) => {
    e.preventDefault()
  }

  const onFileDrop = async (ev) => {
    ev.preventDefault()
    if (ev.dataTransfer.items) {
      const currentFiles = ev?.dataTransfer?.files
      await uploadFiles(currentFiles)
    }
  }

  const nonHiddenFiles = files?.filter(
    (val) => val['delete'] == null || val['delete'] == false,
  )

  return (
    <Container>
      <div className={'row'} css={css(`justify-content: space-between;`)}>
        <InputLabel>{props.label.toUpperCase()}</InputLabel>
        {error && <ErrorSpan>{error}</ErrorSpan>}
        {!props.disabled && nonHiddenFiles && nonHiddenFiles.length > 0 && (
          <UploadLabel onClick={onFileUploadClick}>Upload</UploadLabel>
        )}
      </div>
      <FileUploadContainer
        css={css(error == null ? '' : `border-color: ${Colors.danger};`)}
        onDrop={onFileDrop}
        onDragOver={handleDragOver}>
        <Container onClick={clickBackgroundContainer}>
          {files &&
          files.filter((f) => f['delete'] == undefined || f['delete'] != true)
            .length > 0 ? (
            <></>
          ) : (
            <FileUploadEmptyState
              disabled={props.disabled}
              onClick={onFileUploadClick}
            />
          )}

          <UploadFile
            disabled={props.disabled}
            type="file"
            id={'fileUpload' + props.label}
            multiple
            ref={fileInputField}
            onChange={onFileUpload}
            title=""
            value=""
          />
          <UploadedFilesContainer className={'row'}>
            {nonHiddenFiles?.map((item, index) => (
              <PreviewContainer key={item.name}>
                <FileRow
                  key={item.name}
                  css={css(`background-image: ${getThumbnailForFile(item)};`)}
                  className={'row'}>
                  <RemoveFile
                    css={css(
                      `display: ${(props) =>
                        props.disabled ? 'none' : 'block'};`,
                    )}>
                    <AttachmentDeleteIcon
                      onClick={(e) => onDeleteClick(e, item)}
                    />
                  </RemoveFile>
                </FileRow>
                <BodyText
                  css={css(`
                  text-decoration: underline;
                  cursor: pointer;`)}
                  onClick={(e) => {
                    clickThumbnail(e, item)
                  }}>
                  {item.name}
                </BodyText>
              </PreviewContainer>
            ))}
          </UploadedFilesContainer>
          {isLoading && (
            <>
              <div>Progress: {progress} out of 100.</div>
              <div>Elapsed time: {elapsedTime} seconds.</div>
              <LoadingSpinner />
            </>
          )}
        </Container>
      </FileUploadContainer>
    </Container>
  )
}

I have a following form that I'm validating using Formik:

Form

These validations are controlled by parent level validation schema. Now there is a new requirement that any file uploaded into the "STYLE DETAILS" box should not have special characters in its name (For instance [email protected] should be invalid). A user can drag and drop the file into this component.

What should be the ideal way to approach this? I'm a bit confused because STYLE DETAILS box is not a simple input based component and I'm not sure how I can validate the file name as the file gets uploaded and print the error "Invalid file name" in case validation fails.

Can someone please help me regarding this?

My Form:

<Formik
      initialValues={latestOrder}
      innerRef={formRef}
      // enableReinitialize={true}
      validationSchema={OrderValidationSchema}
      onSubmit={_handleSubmit}>
      {({ values, isSubmitting, errors, dirty, isValid }) => {
        console.log('checking values BasicOrderForm', values, errors)
        return (
          <Form
            css={css(
              `min-width: 500px; max-width: 100%; margin: 0 auto; position: relative;`,
            )}>
            {onBackButtonClick != null && (
              <LabelledPageTitle
                pageTitle="New Order"
                onBackArrowClick={onBack}
                onCloseArrowClick={null}
              />
            )}

            <Spacer height={15} />
            {showProgressBar && (
              <ProgressBar
                items={['Order Info', 'Details', 'Order Submitted']}
                containerStyle={`max-width: 500px;`}
                currentStepIndex={0}
              />
            )}
            <FormBackground hideFormBackgroundColor={hideFormBackgroundColor}>
              {subheading && <Subheading>{subheading}</Subheading>}

              <FormikLabelledTextInput
                label={OrderForm.formField.orderName.label}
                name={OrderForm.formField.orderName.name}
                disabled={readonly}
                placeholder={OrderForm.formField.orderName.placeholder}
              />

              <Spacer height={15} />
              <FormikLabelledDateInput
                disabled={readonly}
                name={OrderForm.formField.expectedDeliveryDate.name}
                label={OrderForm.formField.expectedDeliveryDate.label}
                placeholder={
                  OrderForm.formField.expectedDeliveryDate.placeholder
                }
                minDate={new Date(new Date().getTime() + 86400000)}
                rightDecorativeComponent={<AiOutlineCalendar />}
              />
              <FormikFileUploadComponent
                disabled={readonly}
                name={OrderForm.formField.orderFilesFe.name}
                label={OrderForm.formField.orderFilesFe.label}
              />
              <Spacer height={15} />
              <FormikLabelledTextArea
                disabled={readonly}
                name={OrderForm.formField.outputRequirement.name}
                label={OrderForm.formField.outputRequirement.label}
                placeholder={OrderForm.formField.outputRequirement.placeholder}
              />
              <Spacer height={15} />
              <div className="row">
                <div className="col-6" css={css(`padding-right: 7.5px;`)}>
                  <FormikLabelledMultiSelect
                    name={OrderForm.formField.year.name}
                    disabled={readonly}
                    label={OrderForm.formField.year.label}
                    options={years}
                    notAllowNull="false"
                    placeholder={OrderForm.formField.year.placeholder}
                  />
                </div>
                <div className="col-6" css={css(`padding-left: 7.5px;`)}>
                  <FormikLabelledMultiSelect
                    name={OrderForm.formField.season.name}
                    disabled={readonly}
                    label={OrderForm.formField.season.label}
                    options={SEASONS}
                    notAllowNull="false"
                    placeholder={OrderForm.formField.season.placeholder}
                  />
                </div>
              </div>
              <Spacer height={15} />
              <FormikLabelledDropdown
                disabled={readonly}
                containerStyle={`max-width: unset;`}
                name={OrderForm.formField.threeDSoftware.name}
                label={OrderForm.formField.threeDSoftware.label}
                placeholder={OrderForm.formField.threeDSoftware.placeholder}
                width="100%"
                notAllowNull="false"
                options={software}
              />
              <Spacer height={15} />
              {orderOrganization == null ||
              BRANDS[orderOrganization] == null ? (
                <ErrorSpan
                  css={css(
                    `line-height: 30px; font-size: 16px; font-weight: 600;`,
                  )}>
                  Error: Invalid organization name ({orderOrganization})
                </ErrorSpan>
              ) : (
                <FormikLabelledDropdown
                  disabled={readonly}
                  containerStyle={`max-width: unset;`}
                  name={OrderForm.formField.brandDivision.name}
                  label={OrderForm.formField.brandDivision.label}
                  placeholder={OrderForm.formField.brandDivision.placeholder}
                  width="100%"
                  notAllowNull="false"
                  options={BRANDS[orderOrganization]}
                />
              )}
              <Spacer height={10} />
            </FormBackground>

            {onNextButtonClick == null ? (
              <></>
            ) : (
              <>
                <Spacer height={15} />
                <PrimaryButton
                  isDisabled={
                    //!(isValid && dirty) ||
                    readonly
                  }
                  type={'submit'}
                  onClick={null}>
                  {buttonText ? buttonText : 'Next'}
                </PrimaryButton>
              </>
            )}
            <AcceptOrderButton order={values} allowClick={isValid} />
          </Form>
        )
      }}
    </Formik>

STYLE DETAILS (FileUploadComponent):

const FormikFileUploadComponent: React.FC<IFormikFileUploadComponentProps> = (
  props,
) => {
  const { t, i18n } = useTranslation()
  const fileInputField = useRef(null)
  const [isLoading, setIsLoading] = useState(false)
  const [progress, setProgress] = useState<number>(0)
  const [elapsedTime, setElapsedTime] = useState<number>(0)

  const [field, meta, helpers] = useField(props.name)
  const files = field.value

  const _renderErrorText = () => {
    const error = meta.error
    const touched = meta.touched
    if (error && touched) {
      return error
    } else return null
  }

  const error = props.name ? _renderErrorText() : null

  const onFilesUploadedFn = (files: File[]) => {
    helpers.setValue(files, true)
  }

  const onFileUploadClick = () => {
    let input = document.getElementById('fileUpload' + props.label) as any
    input.value = null
    input.click()
  }

  const onFileUpload = async (e) => {
    let fileList = e?.target?.files
    await uploadFiles(fileList)
  }

  const uploadFiles = async (fileList: File[]) => {
    setIsLoading(true)
    try {
      if (!fileList) {
        setIsLoading(false)
        return
      }

      // Return uploaded files to parent
      let existingFiles = files == null ? [] : files
      let newList = [...existingFiles]

      Array.from(fileList).forEach((newFile, i) => {
        let index = newList.findIndex((t) => t.name == newFile.name)
        if (index > -1) {
          newList[index]['delete'] = false
        } else {
          newList.push(newFile)
        }
      })
      onFilesUploadedFn(newList)

      setIsLoading(false)
    } catch (error) {
      alert(error)
      setIsLoading(false)
    }
  }

  const onDeleteClick = async (e, file: File) => {
    let fileClone = [...files]
    const index = fileClone.findIndex((item) => item.name == file.name)
    if (index > -1) {
      fileClone[index]['delete'] = true
    }
    onFilesUploadedFn(fileClone)
    e.stopPropagation()
  }

  const clickBackgroundContainer = (e) => {
    onFileUploadClick()
  }

  const clickThumbnail = (e, file: File) => {
    e.stopPropagation()
    e.preventDefault()
    if (file['filePath']) {
      downloadFileFromPath(file['filePath'])
    } else {
      window.open(URL.createObjectURL(file))
    }
  }

  const handleDragOver = (e) => {
    e.preventDefault()
  }

  const onFileDrop = async (ev) => {
    ev.preventDefault()
    if (ev.dataTransfer.items) {
      const currentFiles = ev?.dataTransfer?.files
      await uploadFiles(currentFiles)
    }
  }

  const nonHiddenFiles = files?.filter(
    (val) => val['delete'] == null || val['delete'] == false,
  )

  return (
    <Container>
      <div className={'row'} css={css(`justify-content: space-between;`)}>
        <InputLabel>{props.label.toUpperCase()}</InputLabel>
        {error && <ErrorSpan>{error}</ErrorSpan>}
        {!props.disabled && nonHiddenFiles && nonHiddenFiles.length > 0 && (
          <UploadLabel onClick={onFileUploadClick}>Upload</UploadLabel>
        )}
      </div>
      <FileUploadContainer
        css={css(error == null ? '' : `border-color: ${Colors.danger};`)}
        onDrop={onFileDrop}
        onDragOver={handleDragOver}>
        <Container onClick={clickBackgroundContainer}>
          {files &&
          files.filter((f) => f['delete'] == undefined || f['delete'] != true)
            .length > 0 ? (
            <></>
          ) : (
            <FileUploadEmptyState
              disabled={props.disabled}
              onClick={onFileUploadClick}
            />
          )}

          <UploadFile
            disabled={props.disabled}
            type="file"
            id={'fileUpload' + props.label}
            multiple
            ref={fileInputField}
            onChange={onFileUpload}
            title=""
            value=""
          />
          <UploadedFilesContainer className={'row'}>
            {nonHiddenFiles?.map((item, index) => (
              <PreviewContainer key={item.name}>
                <FileRow
                  key={item.name}
                  css={css(`background-image: ${getThumbnailForFile(item)};`)}
                  className={'row'}>
                  <RemoveFile
                    css={css(
                      `display: ${(props) =>
                        props.disabled ? 'none' : 'block'};`,
                    )}>
                    <AttachmentDeleteIcon
                      onClick={(e) => onDeleteClick(e, item)}
                    />
                  </RemoveFile>
                </FileRow>
                <BodyText
                  css={css(`
                  text-decoration: underline;
                  cursor: pointer;`)}
                  onClick={(e) => {
                    clickThumbnail(e, item)
                  }}>
                  {item.name}
                </BodyText>
              </PreviewContainer>
            ))}
          </UploadedFilesContainer>
          {isLoading && (
            <>
              <div>Progress: {progress} out of 100.</div>
              <div>Elapsed time: {elapsedTime} seconds.</div>
              <LoadingSpinner />
            </>
          )}
        </Container>
      </FileUploadContainer>
    </Container>
  )
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文