Редактируемая таблица реагирует

#reactjs

Вопрос:

Я пытаюсь выбрать конкретную строку для редактирования в своей таблице в React.

Мой код выглядит примерно так…

 const [rowData, setRowData] = useState({ kind: { str: '', row: '' }});

const onChange = e => {
  setRowData({...rowData, [e.target.name]: e.target.value }}
}

arr.map((ele, index) => (
  <tr>
    <td><input type='text' name='kind' value={kind.str} row={index} onChange={e => onChange(e)}></td>
  </tr>
))
 

Я написал не весь свой код, но я думаю, что этого должно быть достаточно для ответа.
В основном я нажимаю кнопку edit , которая делает строку доступной для редактирования. Когда я нажимаю эту кнопку, на самом деле все строки и столбцы становятся доступными для редактирования, чего я не хочу, но независимо от этого результата — я перехожу к обновлению таблицы, и обновляется только один ввод, к сожалению, я получаю эту ошибку

 A component is changing a controlled input of type text to be uncontrolled. Input elements should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component
 

Прочитав сообщение об ошибке, я решил изменить свою onChange функцию на эту…

 const onChange = e => {
  setRowData({...rowData, [e.target.name]: { str: e.target.value, row: e.target.getAttribute('row')}}
}
 

вышесказанное решает проблему создания моего controlled компонента uncontrolled , однако все поля ввода в каждой строке, соответствующие kind имени, обновляются, что не является желаемой функциональностью, которую я хочу.

Как я могу решить эту проблему. В конце концов, мне нужна редактируемая таблица, в которой пользователь может обновить одну строку и обновить базу данных с помощью введенных им данных.

arr Переменная, которую я использую, на самом деле имеет имя files .

У меня есть еще один компонент, который получает все мои файлы из моей базы данных и устанавливает их в переменную files

 const [files, setFiles] = useState([]);

useEffect(() => {
    const fetchFiles = async () => {
      setLoading(true);
      const res = await fetch('http://localhost:5000/api/files/all', {
        method: 'GET',
      });
      const data = await res.json();
      setFiles(data);
      setLoading(false);
    };

    fetchFiles();
  }, []);
 

Затем я передаю файлы в качестве реквизита в свой компонент «Элементы», в котором находится код, который я написал для своего вопроса.

Большая часть кода находится здесь…

Компонент списка показов

 const ShowList = () => {
  const [files, setFiles] = useState([]);
  const [loading, setLoading] = useState(false);
  const [currentPage, setCurrentPage] = useState(1);
  const [filesPerPage, setFilesPerPage] = useState(5);
  const [yourUploads, setYourUploads] = useState(false);

  useEffect(() => {
    const fetchFiles = async () => {
      setLoading(true);
      const res = await fetch('http://localhost:5000/api/files/all', {
        method: 'GET',
      });
      const data = await res.json();
      setFiles(data);
      setLoading(false);
    };

    fetchFiles();
  }, []);

  const indexOfLastPage = currentPage * filesPerPage;
  const indexOfFirstPage = indexOfLastPage - filesPerPage;
  const currentFiles = files.slice(indexOfFirstPage, indexOfLastPage);

  const paginate = (pageNumber) => setCurrentPage(pageNumber);

  return (
    <Fragment>
      <div className='container'>
        <h3 className='text-center'>
          {yourUploads ? 'Your uploads' : 'All uploads'}
        </h3>

        <div className='d-flex dropdown'>
          <button
            className='btn mb-3 mr-3'
            type='button'
            id='dropdownMenuButton'
            data-toggle='dropdown'
            aria-haspopup='true'
            aria-expanded='false'
          >
            Pages per row {filesPerPage}
          </button>
          <div className='dropdown-menu' aria-labelledby='dropdownMenuButton'>
            <button
              className='dropdown-item'
              onClick={() => setFilesPerPage(5)}
            >
              5
            </button>
            <button
              className='dropdown-item'
              onClick={() => setFilesPerPage(10)}
            >
              10
            </button>
          </div>
          <div className='dropdown'>
            <button
              className='btn mb-3'
              type='button'
              id='dropdownUploadsButton'
              data-toggle='dropdown'
              aria-haspopup='true'
              aria-expanded='false'
            >
              {yourUploads ? 'your uploads' : 'all uploads'}
            </button>
            <div
              className='dropdown-menu'
              aria-labelledby='dropdownUploadButton'
            >
              <button
                className='dropdown-item'
                onClick={() => setYourUploads(true)}
              >
                your uploads
              </button>
              <button
                className='dropdown-item'
                onClick={() => setYourUploads(false)}
              >
                all uploads
              </button>
            </div>
          </div>
        </div>

        <table id='myTable' className='table table-striped w-100'>
          <thead>
            <tr>
              <th scope='col'>
                <small>Title</small>
              </th>
              <th scope='col'>
                <small>Kind</small>
              </th>
              <th scope='col'>
                <small>Size</small>
              </th>
              <th scope='col'>
                <small>Strength</small>
              </th>
              <th scope='col'>
                <small>Combinations</small>
              </th>
              <th scope='col'>
                <small>Favors</small>
              </th>
              <th scope='col'>
                <small>Stock</small>
              </th>
              <th scope='col'>
                <small>Carousel</small>
              </th>
              <th scope='col'>
                <small>Owner</small>
              </th>
              <th scope='col'>
                <small>Edit</small>
              </th>
              <th scope='col'>
                <small>Delete</small>
              </th>
            </tr>
          </thead>
          <Items
            files={currentFiles}
            loading={loading}
            yourUploads={yourUploads}
          />
        </table>

        <Pagination
          filesPerPage={filesPerPage}
          totalFiles={files.length}
          paginate={paginate}
        />
      </div>
    </Fragment>
  );
};
 

Компонент элементов

 const Items = ({ files, loading, yourUploads }) => {
  const [myUploads, setMyUploads] = useState([]);
  const [editable, setEditable] = useState(false);
  const [rowData, setRowData] = useState({
    kind: { str: '', row: '' },
  });

  const { kind } = rowData;

  useEffect(() => {
    const fetchMyUploads = async () => {
      const res = await fetch('http://localhost:5000/api/files/all/mine', {
        method: 'GET',
      });

      const data = await res.json();

      setMyUploads(data);
    };

    fetchMyUploads();
  }, [files]);

  const onChange = (e) => {
    setRowData({ ...rowData, [e.target.name]: e.target.value });
  };

  const onSubmit = async (e, file_id) => {
    e.preventDefault();
    if (!editable) {
      setEditable(!editable);
    } else {
      await fetch(`http://localhost:5000/api/files/${file_id}`, {
        method: 'PUT',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(rowData),
      });

      setEditable(!editable);
    }
  };

  if (loading) {
    return (
      <tbody>
        <tr>
          <td>loading...</td>
        </tr>
      </tbody>
    );
  }

  const list = yourUploads
    ? myUploads.map((file) => (
        <tr key={file._id}>
          <td>
            <small>{file.title}</small>
          </td>
          <td>
            <small>{file.kind}</small>
          </td>
          <td>
            <small>{file.size}</small>
          </td>
          <td>
            <small>{file.strength}</small>
          </td>
          <td>
            <small>{file.combinations}</small>
          </td>
          <td>
            <small>{file.favors}</small>
          </td>
          <td>
            <small
              className={file.availability ? 'alert-success' : 'alert-danger'}
            >
              {file.availability ? 'in stock' : 'out of stock'}
            </small>
          </td>
          <td>
            <small>{file.isCarousel ? 'carousel' : 'not caorousel'}</small>
          </td>
          <td>
            <small>{file.owner}</small>
          </td>
          <td>
            <button className='btn btn-dark'>
              <small>edit</small>
            </button>
          </td>
          <td>
            <button className='btn btn-danger'>
              <small>delete</small>
            </button>
          </td>
        </tr>
      ))
    : files.map((file, index) => (
        <tr key={file._id}>
          <td>
            <small>{file.title}</small>
          </td>
          <td>
            <small>
              {editable ? (
                <input
                  type='text'
                  name='kind'
                  value={kind.str}
                  row={index}
                  onChange={(e) => onChange(e)}
                />
              ) : (
                file.kind
              )}
            </small>
          </td>
          <td>
            <small>{file.size}</small>
          </td>
          <td>
            <small>{file.strength}</small>
          </td>
          <td>
            <small>{file.combinations}</small>
          </td>
          <td>
            <small>{file.favors}</small>
          </td>
          <td>
            <small
              className={file.availability ? 'alert-success' : 'alert-danger'}
            >
              {file.availability ? 'in stock' : 'out of stock'}
            </small>
          </td>
          <td>
            <small>{file.isCarousel ? 'carousel' : 'not carousel'}</small>
          </td>
          <td>
            <small>{file.owner}</small>
          </td>
          <td>
            <button
              className='btn btn-dark'
              onClick={(e) => onSubmit(e, file._id)}
            >
              <small>{editable ? 'save' : 'edit'}</small>
            </button>
          </td>

          <td>
            <button className='btn btn-danger'>
              <small>delete</small>
            </button>
          </td>
        </tr>
      ));

  return <tbody>{list}</tbody>;
};
 

Комментарии:

1. Я не понимаю, где вы использовали метод onChange.

2. Если вы делаете это не в учебных целях, вы можете использовать библиотеки, такие как react ag-grid , которые будут более элегантными и проверенными в бою.

3. Я делаю это в учебных целях, и я по ошибке пропустил onChange={(e) = onChange(e)} в своем вводе, но он есть в моем коде. Я обновил вопрос

Ответ №1:

Это сообщение об ошибке обычно появляется, когда вы передаете пустое или неопределенное входное значение prop. Если ценностная опора пуста, то react думает, что она стала неконтролируемым компонентом. Поэтому вам нужно исправить ошибку здесь.

 arr.map((ele, index) => (
  <tr>
    <td><input type='text' name='kind' value={kind.str} row={index} onChange={e => onChange(e)}></td>
  </tr>
))
 

Измените значение={kind.str} на значение={rowData.kind.str}

Комментарии:

1. Я уже разрушил, выполнив следующее const {kind} = rowData , Также просто потому, что я попробовал это, но все та же ошибка. Я знаю, что проблема возникает из rowData.kind.row -за того, что, когда я включаю, как я сделал выше, в свой вопрос (вторая версия, которую я сделал onChange ), проблема исчезает, но затем каждая строка, в которой у меня есть поле ввода для вида, обновляется, если бы я просто обновил одну строку, которую я не хочу

2. Это естественное поведение. Поскольку у вас несколько строк, вам нужны данные строки в виде массива, а не одного объекта. Я хотел попросить вас поделиться переменной arr.

3. На самом arr деле это просто files то, что я ввел в свой компонент в качестве реквизита. Это массив объектов. Что-то вроде [{kind: 'somekind', size: 'somesize'}, {}, {}, ...] и так далее.

4. В принципе, я могу получить желаемую функциональность и обновлять правильные строки и поля, но ошибка продолжает появляться. Я также просто попытался поместить строку в качестве поля в данные строки, и внезапно мне удалось изменить текст в поле вида ввода, не изменяя другие поля вида ввода в других строках. Я не уверен, как это на самом деле сработало, но это связано с проблемой того, что строка становится неконтролируемой

5. Является ли arr статическим или динамическим значением? Другими словами, arr также является переменной состояния или реквизита?

Ответ №2:

Спасибо всем за то, что ответили на мой вопрос. Я смог понять это, разделив свои компоненты. Таким образом, из моего Items я визуализирую компонент элемента, который визуализирует элемент, и Edit компонент, который вводит реквизиты только для этой строки. Сделав это, я смог устранить необходимость в row поле, которое, следовательно, избавляет меня от ошибки. Реакция знает, какую строку я редактирую.