Последовательная загрузка файлов React
Я использую react hook form для свой формы. В форме есть четыре поля textarea и один input type="file". Вот дефолтные значения моих полей при открытии окна формы:
{title: '', category: '', description: '', specification: '', uploadFile: Array(0)}
Я добился корректной работы всех textarea, а так же их валидации в зависимости от введенного текста, и т.д. Важно - все мои поля textarea и input являются обязательными. Серьезной проблемой для меня стала загрузка файлов. По логике программы пользователь может подгружать файлы в форму несколько раз ДО сабмита формы. Я сделал свой компонент таким:
import IconContainer from "../IconContainer/IconContainer";
import { UseFormWatch, UseFormSetValue, UseFormReset, Control, Controller, FieldErrorsImpl } from "react-hook-form";
import { UserSubmitForm } from "../../interfaces/inputs";
interface FileInputsProps {
watch: UseFormWatch<UserSubmitForm>;
setValue: UseFormSetValue<UserSubmitForm>
reset: UseFormReset<UserSubmitForm>
control: Control<UserSubmitForm>
errors: Partial<FieldErrorsImpl<UserSubmitForm>>
}
const FileInputs = ({ errors, setValue, control, watch }: FileInputsProps) => {
const allValue = watch();
console.log(allValue);
console.log(errors);
const validateFile = (v:any[]): boolean => {
return false;
};
return (
<div className="modal__formItem">
<label className="modal__input modal__input_files" htmlFor="formId">
<IconContainer icon={"material-symbols:add"} sizeWidth={"15"} sizeHeight={"15"} />
<Controller
name="uploadFile"
control={control}
rules={{
required: {value: true, message: "Обязательное поле"},
validate: (v) => validateFile(v),
}}
render={({ field: { value, name } }) => {
return (
<input
id="formId"
name={name}
type="file"
multiple
hidden
onChange={e => {
if (e.target) {
if (e.target.files) {
setValue("uploadFile", [...value, e.target.files[0]]);
}
}
}}
/>
);
}}
/>
Добавить файл
</label>
<span className="modal__input-limit">pdf, excel, pptx</span>
</div>
);
};
export default FileInputs;
Первая проблема с которой я столкнулся - это некорректная валидация поля загрузки файла. Как видите я попытался принудительно запретить валидацию, попробовав возвращать false из validateFile. Даже при таком ручном запрете, если я ничего не ввожу в textarea и нажимаю submit я получаю корректную валидацию. В консоли при этом я вижу следующее:
Функция Watch() показывает мне корректный список полей, а значит и их регистрацию:
{title: '', category: '', description: '', specification: '', uploadFile: Array(0)}
Но вот попытка просмотреть errors выводит вот такие данные:
{title: {…}, category: {…}, description: {…}, specification: {…}}
Т.е. ошибки uploadFile просто нет при первом submit. Если же нажать submit второй раз, то в errors поле uploadFile появляется:
category
:
{type: 'required', message: 'Обязательное поле', ref: {…}}
description
:
{type: 'required', message: 'Обязательное поле', ref: {…}}
specification
:
{type: 'required', message: 'Обязательное поле', ref: {…}}
title
:
{type: 'required', message: 'Обязательное поле', ref: {…}}
uploadFile
:
{type: 'required', message: 'Обязательное поле', ref: {…}}
Вот ссылка на песочницу. https://codesandbox.io/s/jolly-glade-hhhbdo Как мне корректно валидировать данную форму, чтобы сабмит не срабатывал при первом нажатии
Ответы (1 шт):
полноценный рабочий пример, типизацию только убрал.
export const App = () => {
const { control, handleSubmit, formState, watch } = useForm();
const handleFormSubmit = handleSubmit((data) => {
console.log({ data });
});
return (
<div>
<form
onSubmit={(e) => {
e.preventDefault();
handleFormSubmit(e);
}}
style={{
display: "flex",
flexDirection: "column",
gap: 20,
maxWidth: 400,
width: "100%",
}}
>
<FileFormInput control={control} name="file" />
<TextFormInput control={control} name="name" />
<TextFormInput control={control} name="surname" />
<button>submit</button>
</form>
</div>
);
};
const FileFormInput: FC = ({ control, name: propsName }) => {
const {
field: { onChange, value, name },
fieldState: { error },
} = useController({
control,
name: propsName,
defaultValue: [],
rules: {
required: {
message: "required",
value: true,
},
},
});
return (
<div>
<label
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
width: 200,
height: 50,
border: "1px solid black",
position: "relative",
}}
>
заполни меня
<input
type={"file"}
multiple
style={{
opacity: "0",
width: "100%",
height: "100%",
position: "absolute",
left: 0,
top: 0,
}}
name={name}
onChange={(e) => {
if (e.target?.files?.[0]) {
if (value?.length) {
return onChange([...value, ...Array.from(e.target.files)]);
}
return onChange([e.target.files[0]]);
}
}}
/>
</label>
{error?.message && (
<span
style={{
color: "red",
}}
>
{error.message}
</span>
)}
{value?.map(({ name }) => name).join(", ")}
</div>
);
};
const TextFormInput = ({ control, name: propsName }) => {
const {
field,
fieldState: { error },
} = useController({
control,
name: propsName,
defaultValue: "",
rules: {
required: {
message: "required",
value: true,
},
},
});
return (
<label>
<input type="text" {...field} />
{error?.message && (
<span
style={{
color: "red",
}}
>
{error.message}
</span>
)}
</label>
);
};