可编辑的React表:在组件之间传递数据 [英] Editable React table: pass data between components

查看:61
本文介绍了可编辑的React表:在组件之间传递数据的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

好吧,我是React的新手,需要阅读/引用HTML元素数据属性甚至HTML标记中的内容方面的帮助-通常使用普通的JS(这就是我在React应用程序中所做的事情)像这样:

Ok, I'm new to React and need help in reading/referencing HTML element data attributes or even the content within an HTML tag - normally with plain JS (and this is what I am doing in my React app) I read like this:

const postRow = document.getElementById(tableRowId);
    ;
const author = postRow.getElementsByTagName('td')[0].getAttribute('data-fieldvalue');
const title = postRow.getElementsByTagName('td')[1].getAttribute('data-fieldvalue');
const description = postRow.getElementsByTagName('td')[2].getAttribute('data-fieldvalue');

我有一个叫做Table的功能组件,我像这样使用:

I have a functional component called Table that I use like this:

        <Table
            headers={this.state.postsHeaders}
            rows={this.state.posts}
            editable={this.state.editable}
            deletable={this.state.deletable}
            onUpdateIconClicked={this.toggleUpdatePostModalView}
            onDeleteIconClicked={this.toggleDeletePostModalView}
        />

其中rows属性是我正在使用axios检索的数据.我的表通过几行tr > td行生成了find.单击后,我为每一行都有一个编辑" CTA,我打开了一个模态,在其中传递要为每一行编辑的数据. CTA的Onclick调用此功能后就可以正常工作了:

where rows property is the data that I am retrieving with axios. My table gets generated find with several tr > td rows. I have an 'edit' CTA for each row that on click I open up a modal where I pass my data to be edited for each row. Onclick of the CTA calls this function that works just fine:

    toggleUpdatePostModalView = (postId, tableRowId) => {
    // toggle the confirm delete post view
    let showUpdatePostModal = !this.state.showUpdatePostModal;
    // when postId and tableRowId are both null that means
    // that the view delete confirm modal must remain not
    // visible (closed) so have to override the toggle
    if (postId === null && tableRowId === null) {
        showUpdatePostModal = false;
    }

    const postRow = document.getElementById(tableRowId);
    ;
    const author = postRow.getElementsByTagName('td')[0].getAttribute('data-fieldvalue');
    const title = postRow.getElementsByTagName('td')[1].getAttribute('data-fieldvalue');
    const description = postRow.getElementsByTagName('td')[2].getAttribute('data-fieldvalue');
    // dont get the elements directly like above https://reactjs.org/docs/refs-and-the-dom.html

    this.setState({
        ...this.state,
        showUpdatePostModal: showUpdatePostModal,
        postToUpdate: {
            postId: postId,
            tableRowId: tableRowId,
            author: author,
            title: title,
            description: description
        }
    });
}

有人指出的问题是,由于虚拟DOM和实际DOM同步问题,我不应该使用JS方式获取数据(getElementByIdgetElementsByTagName函数.因此,我被指向 https://reactjs.org/docs/refs-and-the-dom.html ,但是如果我的tr本身是一个组件,这似乎可以工作,但实际上,它只是Table渲染函数中的HTML,如下所示:

The issue that someone pointed out is that I should NOT use the JS way of getting the data (the getElementById and getElementsByTagName functions because of virtual DOM and real DOM sync issues. So I was pointed toward https://reactjs.org/docs/refs-and-the-dom.html but this seems to work if my tr were to be a component itself but as it is, it is just HTML in my Table render function as so:

const table = (props) => {

// the following code creates an array from props.header object
// that is an indexed array (0, 1, ..., n) and each value 
// contains the key properties that compose object props.header,
// and so, even though in .map() the parameter says 'key'
// this is misleading because in reality it is the VALUE 
// (since the key of the array is 0 or 1 or n) but it is  called 
// 'key' because it is the key in the props.headers object that we
// need to get the info for (.map(function(currentValue, index, arr))
const headersArray = Object.keys(props.headers);
const tableHeaders = headersArray.map(key => {
    return <th key={key}>{props.headers[key]}</th>;
});
const editHeader = props.editable === true ? <th key="edit">Edit</th> : null;
const deleteHeader = props.deletable === true ? <th key="delete">Delete</th> : null;

let tableRows = null;
if (props.rows) {

    tableRows = props.rows.map((row, key) => {

        return (
            <tr id={`tr-${key}`} key={key}>
                {/* inner loop to dynamically generate the <td>
                    depending on how many headers there are since
                    each header corresponds to a key or column in
                    the table */}
                {headersArray.map(tdKey => {
                    return <td key={tdKey} data-fieldname={tdKey} data-fieldvalue={row[tdKey]} >{row[tdKey]}</td>
                })}

                {props.editable === true ? <td key="edit"><PencilIcon onClick={() => props.onUpdateIconClicked(row.postId, `tr-${key}`)} /></td> : null}
                {props.deletable === true ? <td className="delete-icon-container" key="delete"><TrashIcon onClick={() => props.onDeleteIconClicked(row.postId, `tr-${key}`)} /></td> : null}
            </tr>
        );

    });

}

return (

    <table className="table is-striped">
        <thead>
            <tr>
                {tableHeaders}
                {editHeader}
                {deleteHeader}
            </tr>
        </thead>

        <tbody>
            {tableRows}
        </tbody>
    </table>

);

}

我还读到,这些引用不应该经常使用-那么如果我有一个包含100行的表怎么办? 200吗我不确定如何进行操作以及以React的方式执行此操作...任何人都可以帮忙吗?

I also read that these refs should not be used to often - so what if I have a table with 100 rows? 200? I am not sure how to proceed and do this the React way...can anyone help?

推荐答案

引用是

Refs are NOT an appropriate tool to use here.

相反,您应该提升状态( 很多 ).

Instead, you should do lifting state up (a lot).

为此,我建议

  • 可以将表组件分解为较小的部分(例如,公共父级<App />中的<Header /><Row />等)和单独的<EditDialog />/<DeleteDialog />组件以编辑/删除行数据)-较小的组件易于维护和故障排除
  • 将表数据(最好是具有唯一记录id的数据)存储在父级(<Table />)组件中,并将与表行相对应的数据条目作为参数传递给<Row />组件
  • 将抽象的onEdit()onDelete()事件处理程序作为道具传递给<Row />组件,并将其附加到 Edit / Delete 按钮的onClick()处理器中
  • 将那些子道具(onEdit()onDelete())绑定到父级中的回调,这将触发编辑/删除对话框
  • 相应地记录<Table />的记录编辑/删除更新状态.
  • to break your table component into smaller pieces (like <Header />, <Row />, etc) within common parent <App /> and a separate <EditDialog />/<DeleteDialog /> components to edit/delete row data) - smaller components are easier to maintain and troubleshoot
  • store your table data (preferably, with unique record id's) within parent (<Table />) component and pass data entries that correspond to table rows as params to <Row /> components
  • pass abstract onEdit() and onDelete() event handlers as props to <Row /> components and attach those to onClick() handlers of Edit/Delete buttons
  • bind those child props (onEdit(), onDelete()) to the callbacks within parent that will trigger editing/deleting dialogs
  • upon record editing/deletion update state of the <Table /> accordingly.

这是上述内容的完整演示(我已使用MaterialUI进行样式设计,以使演示中不包含大量CSS,您可以继续使用自定义组件,希望这不会成为我的示例不太清楚):

Here's the full-blown demo of what's described above (I have used MaterialUI for styling not to overburden the demo with tons of CSS, you may proceed with your custom components, hopefully, that doesn't make my example less clear for you):

const { useState } = React,
      { render } = ReactDOM,
      { TableContainer, Table, TableHead, TableBody, TableRow, TableCell, IconButton, Dialog, DialogTitle, DialogContent, DialogContentText, Button, TextField, FormGroup } = MaterialUI
      
const srcData = [{id:0, author: 'Author1', title: 'Post 1', description: 'Some description'},{id:1, author: 'Author2', title: 'Post 2', description: 'Some other description'},{id:2, author: 'Author3', title: 'Post 3', description: 'Something else'}],
      dataFields = [{id: 0, title: 'Author', key: 'author'},{id: 1, title: 'Title', key: 'title'},{id:2, title: 'Description', key: 'description'}]

const EditButton = ({handleClick}) => (
  <IconButton onClick={handleClick} >
    <i className="material-icons">create</i>
  </IconButton>
)

const DeleteButton = ({handleClick}) => (
  <IconButton onClick={handleClick} >
    <i className="material-icons">delete</i>
  </IconButton>
)
      
const DeleteDialog = ({isOpen, onDialogClose, onConfirmDelete, recordId}) => (
  <Dialog open={isOpen} onClose={onDialogClose} >
    <DialogTitle>Delete record</DialogTitle>
    <DialogContent>
      <DialogContentText>Are you sure you want to delete this record?</DialogContentText>
      <FormGroup>
        <Button onClick={() => onConfirmDelete(recordId)}>Yes</Button>
        <Button onClick={onDialogClose}>No</Button>
      </FormGroup>
    </DialogContent>
  </Dialog>
)

const EditDialog = ({isOpen, onDialogClose, onSubmitEdit, recordData, fields}) => {
  const [data, setData] = useState(),
        handleEdit = (key,value) => setData({...data, [key]:value})
  return (
    <Dialog open={isOpen} onClose={onDialogClose} >
      <DialogTitle>Edit record</DialogTitle>
      <DialogContent>
        <FormGroup>
          {
            fields.map(({key,title}) => (
              <TextField
                key={key}
                defaultValue={recordData[key]}
                label={title}
                onChange={({target:{value}}) => handleEdit(key,value)}
              />
            ))
          }
        </FormGroup>
        <FormGroup>
          <Button onClick={() => onSubmitEdit({...recordData,...data})}>Submit</Button>
          <Button onClick={() => onDialogClose()}>Cancel</Button>
        </FormGroup>
      </DialogContent>
    </Dialog>
  )
}

const Header = ({columnTitles}) => (
  <TableHead>
    <TableRow>
      {columnTitles.map(({title,id}) => <TableCell key={id}>{title}</TableCell>)}
      <TableCell>Action</TableCell>
    </TableRow>
  </TableHead>
)

const Row = ({rowData, columns, onEdit, onDelete}) => (
  <TableRow>
    {columns.map(({key}, i) => <TableCell key={i}>{rowData[key]}</TableCell>)}
    <TableCell>
      <EditButton handleClick={() => onEdit(rowData.id)} />
      <DeleteButton handleClick={() => onDelete(rowData.id)} />
    </TableCell>
  </TableRow>
)

const App = ({data,fields}) => {
  const [tableData, setTableData] = useState(data),
        [dataFields, setDataFields] = useState(fields),
        [deleteDialogOn, setDeleteDialogOn] = useState(false),
        [editDialogOn, setEditDialogOn] = useState(false),
        [recordIdToDelete, setRecordIdToDelete] = useState(),
        [recordIdToEdit, setRecordIdToEdit] = useState(),
        onEditDialogOpen = (id) => (setRecordIdToEdit(id),setEditDialogOn(true)),
        onDeleteDialogOpen = (id) => (setRecordIdToDelete(id), setDeleteDialogOn(true)),
        handleEdit = (data) => {
          setEditDialogOn(false)
          const tableDataCopy = [...tableData],
                editedItemIdx = tableDataCopy.findIndex(({id}) => id == data.id)
                tableDataCopy.splice(editedItemIdx,1,data)
          setTableData(tableDataCopy)
        },
        handleDelete = (idRecordToDelete) => {
          setDeleteDialogOn(false)
          const tableDataCopy = [...tableData]
          setTableData(tableDataCopy.filter(({id}) => id!=recordIdToDelete))
        }
  return (
    <div>
    <DeleteDialog 
      isOpen={deleteDialogOn} 
      onDialogClose={() => setDeleteDialogOn(false)}
      onConfirmDelete={handleDelete}
      recordId={recordIdToDelete}
    />
    <EditDialog 
      isOpen={editDialogOn} 
      onDialogClose={() => setEditDialogOn(false)}
      onSubmitEdit={handleEdit}
      recordData={tableData.find(({id}) => id==recordIdToEdit)||{}}
      fields={dataFields}
    />
    <TableContainer>
    <Table>
        <Header columnTitles={dataFields} /> 
      <TableBody>
        {
          tableData.map(data => (
            <Row 
              key={data.id} 
              rowData={data}
              columns={dataFields}
              onEdit={onEditDialogOpen}
              onDelete={onDeleteDialogOpen}
            />
          ))
        }
      </TableBody>
    </Table>
    </TableContainer>
    </div>
  )
}

render (
  <App data={srcData} fields={dataFields} />,
  document.getElementById('root')
)

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><script src="https://unpkg.com/@material-ui/core@latest/umd/material-ui.development.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.1/axios.min.js"></script><link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"><div id="root"></div>

这篇关于可编辑的React表:在组件之间传递数据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆