Йо-йо! Уже довольно продолжительно я работаю фронтендером. В одном из проектов столкнулся с довольно частой задачей — создание списков файлов для передачи в input.
Такая необходимость возникла из-за работы с библиотекой Formik. Дело в том, что формик работает через так называемый FieldArray, который воспринимает значения как массив и с помощью методов arrayHelpers работает с данными.
Но проблема заключается в том, что если у нас происходят какие-то преобразования с этими фалами (например, очистка инпута от файлов), нам нужно передать новый FileList. В этой статье я расскажу как создавать FileList и работать с arrayHelpers формика.
Для создания файл-листа нам нужно немного кода. В js нет интерфейса для создания FileList, но есть DataTransfer. Итак код:
const dt = new DataTransfer() dt.items.add(ВАШ ФАЙЛ) // Ваш FileList const fileList = dt.files
Если бы у вас был массив фалов, то вы бы могли сделать так:
const dt = new DataTransfer() arrFiles.forEach((file) => { dt.items.add(file) }) const fileList = dt.files
Ну или вообще можно сделать пустой FileList
const dt = new DataTransfer() arrFiles.forEach((file) => { dt.items.add(file) }) const fileList = dt.files
Можно сделать так:
Array.from(FileList)
Ещё вы можете просто пройтись циклом for
for (var i = 0; i < files.length; i++) { // получаем сам файл file = files.item(i); // или можно так file = files[i]; }
Ссылка на MDN
import React, { useRef } from 'react'; import { FieldArray, Formik } from 'formik' import * as yup from 'yup' const App = () => { const inputRef = useRef() /** * Получаем массив файлов из FileList * @param {*} fileList */ const getFileArray = (fileList) => { return Array.from(fileList) } /** * Хелпер для установки занчения в формике * пример будет актуален для инпутов с одним файлом * для множественных значений нужно будет переработать * @param {*} event * @param {*} values * @param {*} arrayHelper */ const handleFileChange = (event, values, arrayHelper) => { const arrFiles = getFileArray(event.target.files) const file = arrFiles.length ? arrFiles[0] : undefined if (!file) return // Проверяем есть ли значения if (Array.isArray(values)) { /** * Заменяем значение по индексу 0 на наш новый файл * обратите внимение что мы вставляем объект { file: File } * так будет лечге валидировать * если нужно будет валифировать другие свойства файла то расширьте объект * и схему валидации * @example { file: File, type: File.type } * * для варианта с множеством файлом предлагаю вам вынести часть * кода с <FieldArray> в отдельный компонент, при передачи пропров * компонент будет рендериться зановов и вы будете всегда работать * с чистым arrayHelper и только всегда пушить */ arrayHelper.replace(0, { file }) } else { // Или вставляем если ещё нет ни одного значения arrayHelper.push({ file }) } /** * Создаём новый fileList, хотя от того можно отказаться * вставляем его в наш ref */ const dt = new DataTransfer() arrFiles.forEach((file) => { dt.items.add(file) }) inputRef.current.files = dt.files } /** * Дополнительное действие при очистке формы * без него может не срабатывать onChange */ const handleDelete = () => { const dt = new DataTransfer() // Тут вставляем пустой FileList inputRef.current.files = dt.files } /** * Валидационная схема * Если вы будете расширять схему валидации для файла * то начните после file: yup..., ВАШ КОД ДАЛЕЕ */ const validationShema = yup.object().shape({ fileInput: yup.array().of(yup.object().shape({ // test('НАЗВАНИЕ ОШИБКИ', 'ОПИСАНИЕ ОШИБКИ', Функция проверки) file: yup.mixed().test('fileSize', 'fileSize', (value) => value ? value.size < 1 : false), })) }) /** * Функция для печати ошибок * для ошибок массивов в errors будет обхект * типа { [НАЗВАНИЕ ОШИБКИ]: ОПИСАНИЕ ОШИБКИ } * @param {*} errors */ const printErrors = (errors) => { if (Array.isArray(errors)) { return errors.map((obj) => Object.values(obj).map((error) => <p key={error}>{error}</p>)) } if (typeof errors === 'string') { return errors } return null } return ( <div> <Formik initialValues={{}} validateOnChange validateOnBlur validateOnMount onSubmit={(values) => console.log(values)} validationSchema={validationShema} > {({ values, errors, isValid, handleSubmit, handleReset }) => ( <> <label htmlFor={`fileInput`}> Загрузить файл </label> { /** * fileInput - такое же как и у инпута с которым работаем */ } <FieldArray name={`fileInput`} render={arrayHelper => ( <> <input ref={inputRef} name={`fileInput`} type={`file`} accept={`.pdf`} onChange={(event) => { handleFileChange(event, values.fileInput, arrayHelper) }} /> {printErrors(errors.fileInput)} </> )} /> <div> <button disabled={!isValid} onClick={handleSubmit} type={`submit`}>Отправить</button> </div> <button onClick={(event) => { handleReset(event) // Обязательно наш хелпер для удаления (переачи нового FileList в input) handleDelete() }} type={`submit`}>Отчистить</button> </> )} </Formik> </div> ); } export default App;
Для тех кто хочет поэксперементировать я создал репозиторий, переходите по ссылке