Не получается прокинуть имя файла из компонента в компонент react(input type file) и установить дату завершения задачи в todo-листе

Есть два ключевых компонента: AddToDo.js и Todo.js. В первом происходит ввод через input заголовка, описания и прикрепление файла через input type="file". Во втором-непосредственное отображение этих данных в виде единой задачи todo. Тут возникает проблема: не получается отобразить имя прикрепленного в AddToDo файла в самом Todo. Пробовала через useState- выводится ошибка, так как input этого типа неконтролируемый. При помощи createRef получилось достать название в AddToDo, но прокинуть название в Todo не получается. Так же нужно установить дату истечения срока выполнения каждой задачи. Хочу доработать тудушку но не понимаю как. Заранее благодарю за помощь!!Прикрепляю ссылку на репозиторий: https://github.com/Kohlenbaron28/todo

//AddToDo.js
import React from "react";
import { db } from "../firebase";
import { collection, addDoc } from "firebase/firestore";

export default function AddTodo() {
  const [title, setTitle] = React.useState("");
  const [about, setAbout] = React.useState("");
  const [file, setFile] = React.useState();


  const handleSubmit = async (e) => {
    e.preventDefault();
    if (title !== "" && about !== "") {
      await addDoc(collection(db, "todos"), {
        title,
        about,
        completed: false,
      });
      setTitle("");
      setAbout("");
    } 
  };
  const fileInput = React.createRef();
  function handleChange(event) {
    event.preventDefault();
    console.log(`Selected file - ${fileInput.current.files[0].name}`);
    setFile(fileInput.current.files[0].name);
    console.log(setFile);
  }
  return (
    <form onSubmit={handleSubmit}>
      <div className="input_container">
        <input
          type="text"
          placeholder="Enter title..."
          value={title}
          onChange={(e) => setTitle(e.target.value)}
        />
        <input
          type="text"
          placeholder="Enter about..."
          value={about}
          onChange={(e) => setAbout(e.target.value)}
        />
        <input
         type="file"
         ref={fileInput}
         value={(e) => e.target.files}
         onChange={handleChange}
         />
      </div>
      <div className="btn_container">
        <button>Add</button>
      </div>
    </form>
  );
}
//Todo.js
import React from "react";
import CheckCircleIcon from "@mui/icons-material/CheckCircle";
import EditIcon from "@mui/icons-material/Edit";
import DeleteIcon from "@mui/icons-material/Delete";

export default function Todo({ todo, toggleComplete, handleDelete, handleEdit,}) {
  const [newTitle, setNewTitle] = React.useState(todo.title);
  const [newAbout, setNewAbout] = React.useState(todo.about);
  const [newFile, setNewFile] = React.useState(todo.setFile); 

  const handleChangeTitle =  (e) => {
    e.preventDefault();
    if (todo.complete === true) {
      setNewTitle(todo.title);  
    } else {
      todo.title = "";
      setNewTitle(e.target.value); 
    }   
  };
  const handleChangeAbout = (e) => {
    e.preventDefault();
    if (todo.complete === true) {
      setNewAbout(todo.about);  
    } else {
      todo.about = "";
      setNewAbout(e.target.value); 
    }   
  };

  return (
    <div className="todo">
      <input
        style={{ textDecoration: todo.completed && "line-through" }}
        type="text"
        value={todo.title === "" ? newTitle : todo.title}
        className="list"
        onChange={handleChangeTitle}
      />
          <input
        style={{ textDecoration: todo.completed && "line-through" }}
        type="text"
        value={todo.about === "" ? newAbout : todo.about}
        className="list-about"
        onChange={handleChangeAbout}
      />
      <div>
        <input type="file"/>
        <button
          className="button-complete"
          onClick={() => toggleComplete(todo)}
        >
          <CheckCircleIcon id="i" />
        </button>
        <button
          className="button-edit"
          onClick={() => handleEdit(todo, newTitle, newAbout)}
        >
          <EditIcon id="i" />
        </button>
        <button className="button-delete" onClick={() => handleDelete(todo.id)}>
          <DeleteIcon id="i" />
        </button>
      </div>
    </div>
  );
}
//App.js
import AddToDo from './components/AddToDo';
import './App.css';
import React from 'react';
import Title from './components/Title';
import Todo from './components/Todo';
import {
    collection,
    query,
    onSnapshot,
    doc,
    updateDoc,
    deleteDoc,
} from "firebase/firestore";
import { db } from "./firebase";

function App() {
    const [todos, setTodos] = React.useState([]);
  
    React.useEffect(() => {
      const q = query(collection(db, "todos"));
      const unsub = onSnapshot(q, (querySnapshot) => {
        let todosArray = [];
        querySnapshot.forEach((doc) => {
          todosArray.push({ ...doc.data(), id: doc.id });
        });
        setTodos(todosArray);
      });
      return () => unsub();
    }, []);
  
    const handleEdit = async (todo, title, about) => {
      await updateDoc(doc(db, "todos", todo.id), { title: title, about: about});
    };
    const toggleComplete = async (todo) => {
      await updateDoc(doc(db, "todos", todo.id), { completed: !todo.completed });
    };
    const handleDelete = async (id) => {
      await deleteDoc(doc(db, "todos", id));
    };
  return (
    <div className="App">
      <div>
        <Title/>
      </div>
      <div>
        <AddToDo/>
      </div>
      <div className='todo_container'>
      {todos.map((todo) => (
          <Todo
            key={todo.id}
            todo={todo}
            toggleComplete={toggleComplete}
            handleDelete={handleDelete}
            handleEdit={handleEdit}
          />
        ))}
      </div>
    </div>
  );
}

export default App;

Ответы (1 шт):

Автор решения: Daniil Loban

Если речь идет про список имен файлов, то прежде всего нужно позаботится о том чтобы они добавлялись в базу, в данном случае, я так понял это только строки имен, хранение самих файлов в firebase лучше обеспечить через store, либо их придется переводить в base64 со всеми вытекающими последствиями.

const [files, setFiles] = useState();
const fileInput = useRef(null)

const handleSubmit = async (e) => {
  e.preventDefault();
  if (title !== "" && about !== "") {
    handleAddTodo({
      title,
      about,
      completed: false,
      files
    })
    setTitle("");
    setAbout("");
    fileInput.current.value = null; // очищаем input от файлов
  }
};

handleAddTodo в данном случае находится в App где рендерится сам список

Для того чтобы можно было выбрать несколько файлов нужно использовать атрибут multiple:

<input
  type="file"
  multiple={true}
  ref={fileInput}
  onChange={handleChange}
 />

(e) => e.target.files не может быть валидным значением для value у input так как там можно хранить лишь строковые (или числовые типы, но в конкретном случае они не используются)

Помещать в стейт имена файлов можно так (preventDefault не нужен):

function handleChange({target}) {
  const fileNames  = Array.from(target.files).map(file => file.name)
  console.log(`Selected files - ${fileNames.join(', ')}`);
  setFiles(fileNames);
} 

Пример вывода:

введите сюда описание изображения

На всякий случай добавлю, что браузер сам не имеет возможности обращаться к локальным файлам если вы посмотрите на value в input то увидите что-то типа C:\fakepath\some_file.txt — и это при том что у меня linux и диск C отстутсвует по причине иной файловой системы. Поэтому если Вы впоследствии захотите взаимодействовать с содержимым файлов, вам нужно сохранять их в Облачное хранилище для Firebase и подгружать на клиент.

→ Ссылка