反应:如何使用formik从父验验证子组件值
我有以下表格,我使用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:
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 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论