Как передать файл между компонентами React: Zustand и Redux

    При загрузке файлов через <input type="file"> разработчики часто сталкиваются с проблемой передачи объекта File другим компонентам. Сохранить event.target.files[0] в глобальном стейт-менеджере (Zustand, Redux) не получается, так как File не сериализуется - его нельзя представить в виде строки JSON. В этой статье мы объясняем, почему так происходит, и предлагаем проверенные решения.

    Почему File нельзя сохранить в Redux или Zustand

    Объект File наследуется от Blob и содержит бинарные данные. Стейт-менеджеры, такие как Redux и Zustand, по умолчанию сериализуют состояние в JSON. File не имеет стандартного JSON-представления - при попытке вызвать JSON.stringify(file) вы получите пустой объект {}. Это приводит к потере данных при передаче.

    Решение: хранить File в useState и передавать пропсами

    Самый простой и надёжный способ - использовать локальный useState в родительском компоненте и передавать файл дочерним элементам через пропсы. Это не «ужасное решение», а стандартная практика React, которая сохраняет ссылку на бинарный объект. Если структура компонентов становится слишком глубокой, подумайте о рефакторинге с использованием Context API или кастомного хука.

    Пример с Context API

    const FileContext = React.createContext();
    
    function FileProvider({ children }) {
      const [file, setFile] = useState(null);
      return (
        <FileContext.Provider value={{ file, setFile }}>
          {children}
        </FileContext.Provider>
      );
    }

    Такой подход позволяет избежать «пробрасывания пропсов» (prop drilling) и работает с любыми бинарными данными.

    Альтернатива: преобразование File в base64

    Если вам обязательно нужно хранить файл в глобальном сторе (например, для сохранения между перезагрузками страницы), конвертируйте его в строку base64 с помощью FileReader:

    function fileToBase64(file) {
      return new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.readAsDataURL(file);
        reader.onload = () => resolve(reader.result);
        reader.onerror = reject;
      });
    }

    После конвертации вы можете сохранить строку base64 в Zustand или Redux, а при необходимости восстановить файл (например, для отображения превью). Недостаток: увеличение размера данных на ~33% и возможные проблемы с производительностью для больших файлов.

    Использование Zustand с File: практический подход

    Zustand не требует сериализации, если вы храните файл в состоянии без промежуточного преобразования. Просто сохраните ссылку на объект File:

    import { create } from 'zustand';
    
    const useFileStore = create((set) => ({
      file: null,
      setFile: (file) => set({ file }),
    }));

    Это работает, потому что Zustand не сериализует состояние автоматически - данные остаются в памяти как JavaScript-объекты. Важно: при использовании persist middleware (для localStorage) вам придётся конвертировать файл в base64, так как localStorage поддерживает только строки.

    Рекомендации по архитектуре

    • Для небольших проектов: используйте useState и пропсы - это просто и предсказуемо.
    • Для средних проектов: внедрите Context API или Zustand без persist.
    • Для крупных приложений: рассмотрите хранение файлов в IndexedDB через библиотеки типа idb-keyval, а в сторе держите только метаданные (id, имя, размер).

    Помните: File - это ссылка на бинарные данные в памяти браузера. Не пытайтесь сериализовать его в JSON, а работайте с ним как с объектом. Выберите подход, который соответствует вашим требованиям к персистентности и производительности.

    Часто задаваемые вопросы