更新最初源自远程数据的单个 material-table 行

Update a single material-table row that is originally sourced from remote data

我有一个 React material-table。我希望其中一列是我的自定义 <DatePicker/> 组件,用户可以在其中为该行分配预定日期。 table currentley 远程获取数据。

当使用 <DatePicker/> 组件并选择日期时,我不希望整个 table 从远程源重新获取数据。相反,我希望我的 <DatePicker/> onChange 处理程序修改 table.

中的当前数据(我正在设置日期的行)

有没有办法利用 material-table 的数据存储并调用“setData”函数,以便我可以只更新目标行?

通常,如果 table 的数据不是来自远程源,我会调用自己的“setData”函数,但因为我正在利用 material-tables 内置远程数据选项并使用过滤器我不能这样做。

我无法找到一种干净的方法来实现利用 material-table 的数据并在数据来自远程源时更新行。但是,我能够想出一个有点 'hacky' 的解决方案。

为此我做了以下工作:

  1. 创建了 table对 material-table 的引用并将其存储在我的 Redux 商店中。
  2. 创建了本地组件状态来存储我之前的查询参数历史记录。
  3. 将 MaterialTable 上的 data 属性设置为自定义 getData 函数,return 是一个 Promise,可以点击后端 api 获取 table 数据(所有行),或修改当前 table 数据然后 return 这个新修改的数据。此函数使用 tableRef.current.dataManager.getRenderState().data 获取当前 MaterialTable 数据。然后 return 是它的修改版本(如果需要)。
  4. 创建了一个小组件 (NextAction),它采用 2 个道具 - rowData 来自 MaterialTable 和 tableRef 来自 Redux 商店 - 渲染包含我的自定义操作按钮的列,单击该按钮可以修改当前 table 数据。该组件还可以访问存储在 Redux 存储中的 material-table 的 tableRef。该组件(最终是一个按钮)利用 tableRef 上的 onQueryChange() 函数手动强制调用 getData 函数。
  5. 将我的列定义中的呈现键设置为 return 这个名为 NextAction 的组件。

我们的想法是创建一个带有按钮的列。当按下按钮时,该行的 'current_state' 列被修改,并且 table 数据不是来自 api.

的 re-rendered

这是有效的,因为单击按钮时会调用 tableRef.current.onQueryChange() 函数。因为组件的 'data' 属性设置为自定义 getData 函数,所以当 tableRef.current.onQueryChange() 被调用时,MaterialTable 被告知调用我们在 MaterialTable 组件上设置的 data 函数到我们的自定义 getData 函数。

在这个 getData 函数中,我们然后检查新查询参数是否与旧查询参数不同(因为查询参数也存储在本地状态中)。如果它们相同,我们检查 getData 查询参数是否有 nextState 键。此 nextState 键仅在从自定义按钮调用 getData 时出现。如果是,我们使用 tableRef 访问当前的 table 数据,修改我们想要的行,然后 return 新的 tableData(仍然包裹在一个承诺)。

下面是一些实现上述功能的示例代码:

import React, { useContext } from 'react';
import { connect } from 'react-redux';
import axios from 'my/path/to/axios';

/*
  Custom Button  rendered in a column on the MaterialTable
  Takes a tableRef prop from the Redux Store.
  Note the onClick function that is called.
*/
const NextActionButton = connect(mapStateToPropsNextAction)(( props ) => {
  const tableRef = props.tableRef; // captured from redux store - tableRef from MaterialTable
  return (
    <Button
      disabled={false}
      variant="contained"
      color="secondary"
      size="large"
      onClick={(e) => {tableRef.current.onQueryChange({
        onNextAction: true,
        nextState: 'dispatched',
        row_id: props.rowData.tableData.id,
      })}}
      >
      Dispatch
    </Button>
  )
})

const MyComponent = ( props ) => {

  let tableRef = React.createRef()  // Create the tableRef
  props.setTableRef(tableRef)

  // Store the query history
  const [queryHist, setQueryHist] = React.useState({});

  // Custom function to return the table data either via api or from the old state with modifications
  const getData = (query, tableRef) => new Promise((resolve, reject) => {
    const queryParams = {
      filters: query.filters,
      orderBy: query.orderBy,
      orderDirection: query.orderDirection,
      page: query.page,
      pageSize: query.pageSize,
      search: query.search,
    }

    /*
    Here is the magic that allows us to update the current tableData state without hitting the api

    If the query is the same as before, then just return the same table without hitting api
    This occurs only when:
      1.) the user types the same filter
      2.) when the user wants to update a single row through the special update component in the update column
    */
    if (JSON.stringify(queryHist) === JSON.stringify(queryParams)) {
      // This is how we get MaterialTable's current table data. This is a list of objects, each object is a row.
      let newData = tableRef.current.dataManager.getRenderState().data; 
      if (query.onNextAction === true) {
        newData[query.row_id].current_state = query.nextState;
      }
      resolve({
        data: newData,
        page: tableRef.current.state.query.page,
        totalCount: tableRef.current.state.query.totalCount,
      })
    }

    setQueryHist(queryParams)  // Store query params in the local state for future comparison

    axios.get('/my_data_api', {
      params: queryParams
    }).then(response => {
        return resolve({
          data: response.data.data,
          page: response.data.page,
          totalCount: response.data.total_count,
        })
      }).catch(function (error) {
        console.log(error);
      })
  })

  const [columns] = React.useState([
    {
      title: "Record Id",
      field: "record_id",
    },
    {
      title: "Current State",
      field: "current_state",  // This is the field we will be updating with the NextActionButton
    },
    {
      title: "Next Action",
      field: "placeholder",
      filtering: false,
      render: (rowData) =>  <NextActionButton rowData={rowData}/> // Render the custom button component
    },
  ])

  return (
    <MaterialTable
      title="My Table Title"
      tableRef={tableRef}  // Assign the ref to MaterialTable
      data={
        (query) => getData(query, tableRef)
      }
      columns={columns}
      options={{
        filtering: true,
        debounceInterval: 600,
        paging: true,
        pageSize: 50,
        pageSizeOptions: [50, 100, 1000],
        emptyRowsWhenPaging: false,
        selection: true,
      }}
      .
      .
      .

    />
  )
}

// Redux stuff
const mapDispatchToProps = dispatch => {
  return {
    setTableRef: (tableRef) => dispatch({type: 'SET_TABLE_REF', 'ref': tableRef})
    .
    .
    .
  };
};

export default connect(null, mapDispatchToProps)(Workorders);