Оригинальная продуктовая задача, помимо непосредственной загрузки файлов и dnd, включала в себя ещё 2 больших куска логики: Динамическая галерея для отображения загружаемых и загруженных файлов с их статусами. Резолв конфликтов дубликатов имён файлов при загрузке. Т.е. надо было при загрузке файлов дать пользователю возможность выбрать один из возможных сценариев загрузки: переименовать загружемые файлы; переименовать уже загруженные файлы; остановить загрузку. Реализация функционала резолва конфликтов слишком объёмная для этой статьи. Поэтому данный функционал оставлен за пределами этой публикации. Если вам, дорогие читатели, будет интересно, намекните в комментариях - оформим отдельной публикацией как будет минутка времени) Итак, поехали! В данной статье описано как написать загрузчик файлов, который удовлетворяет следующим требованиям: Функциональные требования загрузчик умеет валидировать загружаемые файлы (не более 10мб и разрешением не более 25МП); загрузчик должен предоставлять 2 элемента для инициации загрузки (кнопку "загрузить" и dnd область для загрузки); загрузчик умеет отслеживать и предоставлять как общие статусы загрузки, так и статусы загрузки каждого конкретного файла. Нефункциональные требования загрузчик должен заниматься только загрузкой файлов. Отображение и любой другой функционал должны быть вынесены за скобки этого компонента; должна быть возможность располагать элементы управления загрузчиком на разных уровнях вложенности. Прежде чем перейдём к реализации фичи, обговорим зависимости (т.е. уже реализованные решения, которые помогут упростить реализацию этой фичи). Их в данном случае 2 штуки: Валидатор fileMaxSize в связке с утилиткой imageMaxResolution хук useFilesFormField Комбинация этих зависимостей поможет нам легко и быстро реализовать функциональные требования, связанные с валидацией загружаемых файлов в контексте react приложения. Подробнее про реализацию валидации можно прочитать в моей статье про Валидацию форм без зависимостей. Загрузчик будет представлять из себя компонент обёртку, которая внутри себя определяет React.Context и отдаёт его вниз для всех своих children. При реализации мы будем использовать подход Compound components для того, чтобы не раскидывать загрузчик и элементы управления загрузчиком по множеству каталогов/файлов. План реализации компонента выглядит следующим образом: Создать контекст, который сделает доступным интерфейс загрузчика для его children. Описать сущности, с которыми будем работать. Описать интерфейс компонента, через который будем отдавать информацию по текущим статусам загрузки. Реализовать загрузку файлов. Реализовать обработчик выбора файлов. Создать компонент Input для инициации загрузки. Создать компонент Dnd для инициации загрузки. Собрать всё вместе и проверить, что закрыли все функциональные/нефункциональные требования. Будем использовать react контекст для того, чтобы можно было использовать несколько элементов инициации загрузки (кнопка и dnd). А так же это даёт возможность расположить элементы управления загрузчиком на любом уровне вложенности компонентов. Затем пишем небольшой кастомный хук, который позволит удобно юзать наш контекст и будет контролировать корректные условия запуска (элемент управления загрузчиком не может быть использован вне контекста загрузчика). Для использования контекста загрузчика файлов нам просто нужно будет использовать хук useFilesUploader В общей сложности имеется только 2 сущности, которыми загрузчику файлов нужно оперировать: загружаемый файл результат загрузки Перед тем, как перейдём к описанию корневых сущностей опишем статусы (состояния), в которых может находится наш загружаемый файл: Затем определим интерфейсы непосредственно загружаемого файла UploadEntry и результат загрузки UploadResult Поля name, status, error в пояснении не нуждаются. А вот в uploaded мы будем хранить информацию по нашему успешно загруженному файлу (ответ от нашей ручки api). Часто это может быть полезно, так как в момент добавления файла в хранилище на сервере, ему могут быть даны уникальные признаки (идентификаторы), которые возможно будет нужно использовать на клиентской части (например в кейсах, когда потребуется разрешать конфликты дубликатов загружаемых файлов) MediaFilesResponse может быть любым. Этот тип описывает возвращаемое значение от вашего api endpoint. В моём конкретно случае, ответ имеет следующую структуру: PS: директива as const используется в typescript для сообщения транспилятору, о том, что ключи данного объекта не будут изменятся. Это даёт возможность нам сделать union type по ключам этого объекта с помощью комбинации операторов keyof typeof структура UploadResult по сути описывает результат Promise.allSettled В наших функциональных требованиях мы указывали, что наш компонент, должен уметь сообщать о состоянии процесса загрузки. В данной реализации, помимо стандартных "начали/закончили" загрузку, мы предусмотрим возможность отслеживания окончания загрузки каждого конкретного файла, а так же возможность сделать что-то перед стартом загрузки (но уже после выбора файлов). Совершенно типовой хендлер, который подхватывает загруженные пользователем файлы, создаёт на их основе массив файлов (ведь по умолчанию из  мы получаем FileList) отправляет эти данные в handleChange хука useFilesFormField Стоит обратить внимание на строчку Таким оборазом мы сбрасываем input value, для того, чтобы не блокировать повторный выбор (и загрузку) файлов с одинаковыми именами. Это может показаться несущественным, но реализация этого функционала в итоге может значительно облегчить конечному пользователю флоу работы с загрузчиком - у него будет возможность редактировать файлы на локальной машине и грузить их повторно. Загрузку файлов будем реализовывать через useEffect. При каждом изменении значений или итогов валидации наших загружаемых файлов, мы будем пытаться загрузить все валидные файлы. Ну и не забываем завести фаложок isUpLoading для отображения индикации процесса загрузки. PS: formatToUploadEntries это функция, которая трансформирует (подготавливает) данные к отправке наверх, согласно нашим типам: Инпут для загрузки файлов штука совсем тривиальная, суть которой сводится к тому, чтобы подхватить контекст (через уже объявленный в FilesUploader хук useFilesUploader) и использовать данные оттуда для управления загрузчиком. Мы уже запилили всё самое сложное. Осталось сделать перетягивалку для файлов, чтобы наши пользователи имели привычный многим интерфейс загрузки. Непосредственная реализация DND описна в Крошечном рецепте приготовления react-dnd. Здесь мы просто соединяем нашу DND фичу с загрузчиком файлов. Первое, что нам понадобится это добавить обработчик для dnd функционала в наш контекст Затем нужно написать реализацию обработчика уже в самом компоненте: И в итоге осталось создать немного разметки и подсоединить наш dnd контейнер к нашему загрузчику через useFilesUploader Таким образом вместе с загрузчиком, мы поставляем 2 элемента управления: FilesUploader.Input и FilesUploader.DnD. Они могут быть расположены где угодно в рамках нашего контекста FilesUploader В теории мы можем по этому же паттерну сгенерировать сколько угодно элементов управления. Наш компонент FilesUploader (вернее файл, его содержащий) будет по итогу выглядеть следующим образом: Теперь мы можем просто обернуть приложение в контекст FilesUploader и использовать FilesUploader.Input и FilesUploader.DnD на любом уровне вложенности. Например: Пропсы onFilesSelected, onFilesCleared, onUploadStart, onFileUploadEnd дают возможность в рутовом компоненте получать актуальную информацию о статусе/прогрессе как всего процесса загрузки, так и конкретного элемента. Это даст возможность работать с визуальным отображением загружаемых файлов. Таким образом мы полностью закрыли требования к нашему загрузчику: Фунцкиональные требования загрузчик умеет валидировать загружаемые файлы; загрузчик предоставляет 2 элемента для инициации загрузки (кнопку "загрузить" и dnd область для загрузки); загрузчик умеет отслеживать и предоставлять как общие статусы загрузки, так и статусы загрузки каждого конкретного файла. Нефункциональные требования логика загрузчика сфокусирована только на загрузке файлов. Отображение оставлено на откуп компоненту контейнеру, который содержит в себе загрузчик; использование контекста даёт возможность располагать элементы управления загрузчиком на разных уровнях вложенности (в рамках контекста загрузчика). Спасибо за чтение и удачи в реализации ваших собственных загрузчиков ). PS: Ссылки из статьи Зависимости  Крошечный рецепт приготовления react-dnd Валидация форм без зависимостей понятным языком про Compound components про Javascript  про Promise.allSettled про FileList про Typescript  про as const про keyof typeof про union type Senior frontend engineer