0

我正在尝试针对特定行在 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();
  }, []);

然后,我将作为道具文件传递给我的 Items 组件,这是我为我的问题编写的代码所在的位置。

大部分代码都在这里...

ShowList 组件

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>;
};
4

2 回答 2

0

谢谢大家回答我的问题。我能够通过拆分组件来解决这个问题。因此,我从我Items的渲染中渲染了一个渲染项目的 Item 组件和一个Edit仅为该行引入道具的组件。通过这样做,我能够消除对row字段的需求,从而消除了我的错误。React 知道我正在编辑哪一行。

于 2020-08-13T16:17:35.457 回答
0

当您将 null 或 undefined 传递给输入值 prop 时,通常会出现此错误消息。如果 value prop 为空,则 react 认为它成为不受控制的组件。所以你需要在这里修复错误。

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

将 value={kind.str} 更改为 value={rowData.kind.str}

于 2020-08-12T19:37:50.080 回答