【问题标题】:one react component updates all the other react components一个反应组件更新所有其他反应组件
【发布时间】:2019-10-01 09:39:14
【问题描述】:

我有一个反应表,其中一列是另一个组件。这个组件是一个下拉列表,它通过我在 componentDidMount() 中定义的 API 调用获取其值。

我有一个用例,如果用户从下拉列表中选择任何值,我想将该字段保存到数据库。所以我在下拉列表的handleChange函数中定义了这个post call。

问题是,当我更改任何一行中的值时,其他行中的每个其他组件也会调用在 componentDidMount() 中定义的网络调用。因此,所有 4 个条目都调用了 componentDidMount()。我也在服务器端确认了。我可以看到四个获取请求(我现在只有 4 行)。我非常困惑为什么它会这样?

父组件

import React from 'react';
import ReactTable from 'react-table';
import 'react-table/react-table.css';
import Popup from "reactjs-popup";

export default class DetailsTable extends React.Component {

  constructor(props, context) {
    super(props, context);

    this.state = {
      shipmentDataMap : { },
      selectedRow: null,
      downloadableAlerts: []
    };
    this.setState = this.setState.bind(this);
    this.handleRowClick = this.handleRowClick.bind(this);
    this.handleReassignment = this.handleReassignment.bind(this);
    this.handleStatusUpdate = this.handleStatusUpdate.bind(this);
    this.generateFilteredArr = this.generateFilteredArr.bind(this);
    this.handleDownload = this.handleDownload.bind(this);
    this.updateActualEntity = this.updateActualEntity.bind(this);
  };


 componentDidMount() {
         axios.post('/entity/getRoute', {
           trackingId: this.state.tid
         })
         .then((response) => {
           let tempRoute = [];
           response.data.route.forEach(element => {
             tempRoute.push({ label: element['node'], value: element['node'] });
           })
           this.setState({route: tempRoute});
         })
         .catch(function (error) {
           console.log(error);
         });
       };

    updateActualEntity = (trackingId, updatedEntity) => {
    let updatedRecord = this.state.shipmentDataMap[trackingId];
    updatedRecord.actualEntity = updatedEntity;
    this.setState({shipmentDataMap: this.state.shipmentDataMap});
  };

render() {
    const TableColumns = [{
        Header: 'Actions',
        id: 'actionPopupButton',
        filterable: false,
        style: {'textAlign': 'left'},
        Cell: row => (<div><ReassignPopup data={row.original} updateRowFunc={this.handleReassignment} nodeOptions={this.props.nodeOptions}/> 
                        <br/>
                        <UpdateStatusPopup data={row.original} updateRowFunc={this.handleStatusUpdate} statusOptions={this.props.statusOptions}/>
                        </div>)
      },
      {
        Header: 'Assigned Node',
        headerStyle: {'whiteSpace': 'unset'},
        accessor: 'node',
        style: {'whiteSpace': 'unset'}
      }, {
        Header: 'TID',
        headerStyle: {'whiteSpace': 'unset'},
        accessor: 'tid',
        width: 140,
        filterMethod: (filter, row) => {
                    return row[filter.id].startsWith(filter.value)
                  },
        Cell: props => <a href={`https://eagleeye-eu.amazon.com/search?type=Scannable&display=leg&value=${props.value}`} target="_blank" rel="noopener noreferrer">{props.value}</a>
      },
      {
        Header: 'Predicted Entity',
        headerStyle: {'whiteSpace': 'unset'},
        filterable: false,
        accessor: 'predictedEntity',
        style: {'whiteSpace': 'unset'},
      },
      {
        Header: 'Feedback',
        headerStyle: {'whiteSpace': 'unset'},
        filterable: false,
        accessor: 'actualEntity',
        width: 140,
        style: {'whiteSpace': 'unset', overflow: 'visible'},
        Cell: row => (<div><AbusiveEntityComponent entity={row.original.actualEntity}
                  tid={row.original.tid} trackingDetailsId={row.original.trackingDetailsId}
                  updateActualEntityInShipmentData={this.updateActualEntity}/></div>)
      }

      return <div> 
    <CSVLink data={this.state.downloadableAlerts} filename="ShipmentAlerts.csv" className="hidden" ref={(r) => this.csvLink = r} target="_blank"/>
    <ReactTable
      ref={(r)=>this.reactTable=r}
      className='-striped -highlight'
      filterable
      data={Object.values(this.state.shipmentDataMap)}
      //resolveData={data => data.map(row => row)}
      columns={TableColumns}
      //filtered={this.state.filtered}
      filtered={this.generateFilteredArr(this.props.filterMap, this.props.searchParams)}
      /*onFilteredChange={(filtered, column, value) => {
        this.onFilteredChangeCustom(value, column.id || column.accessor);
      }}*/
      defaultFilterMethod={(filter, row, column) => {
            const id = filter.pivotId || filter.id;
            if (typeof filter.value === "object") {
              return row[id] !== undefined
                ? filter.value.indexOf(row[id].toString()) > -1
                : true;
            } else {
              return row[id] !== undefined
                ? String(row[id]).indexOf(filter.value) > -1
                : true;
            }
          }}

      defaultPageSize={10}
      //pageSize={10}
      previousText='Previous Page'
      nextText='Next Page'
      noDataText='No intervention alerts found'
      style={{
            fontSize: "12px",
            height: "67.4vh" // Using fixed pixels/limited height will force the table body to overflow and scroll
          }}
      getTheadFilterProps={() => {return {style: {display: "none" }}}}
      getTbodyProps={() => {return {style: {overflowX: "hidden" }}}} //For preventing extra scrollbar in Firefox/Safari
      /*
      getTrProps={(state, rowInfo) => {
        if (rowInfo && rowInfo.row) {
          return {
            onClick: (e) => {this.handleRowClick(e, rowInfo)},
            style: {
                  //background: rowInfo.index === this.state.selectedRow ? '#00afec' : 'white',
                  color: rowInfo.index === this.state.selectedRow ? 'blue' : 'black'
                }    
          }
        } else {
          return {}
        }
      }
    } */
      />
  </div>;
  }
}

子组件

import React from 'react';
import axios from 'axios';

export default class AbusiveEntityComponent extends React.Component {
  constructor(props, context) {
    super(props, context);
    this.state = {
        entity: this.props.entity,
      tid: this.props.tid,
      trackingDetailsId: this.props.trackingDetailsId,
      route: []
    };

    this.handleChange = this.handleChange.bind(this);
  }

  handleChange = (event) => {
    var selected = event.target.value;
    if(selected !== '' && this.state.entity !== selected) {
      if (window.confirm('Are you sure you want to select: '+ selected)) {
        axios.post('/entity/upateAbusiveEntity', {
        trackingDetailsId: this.state.trackingDetailsId,
        abusiveEntity: selected
      }).then( (response) =>{
        this.setState({entity: selected});
        this.props.updateActualEntityInShipmentData(this.state.tid, selected);
      })
      .catch(function (error) {
        console.log(error);
      });
      }
    }
  }

  componentDidMount() {
    console.log("did mount");
    axios.get('/entity/getRoute', {
      params: {
        trackingId: this.state.tid
      }
    })
    .then((response) => {
      let tempRoute = [];
      let prev="";
      response.data.route.forEach(element => {
        if(prev!== "") {
          tempRoute.push(prev+"-"+element['node'])
        }
        tempRoute.push(element['node']);
        prev=element['node'];
      })
      this.setState({route: [''].concat(tempRoute)});
    })
    .catch(function (error) {
      console.log(error);
    });
  };

  render() {
    return (
      <div className="AbusiveEntityDiv">
         <select onChange={this.handleChange} value={this.state.entity===null?'':this.state.entity} 
            style={{width: 100}}>
          { this.state.route.map(value => <option key={value} value={value}>{value}</option>) }
         </select>
      </div>
    );
  }
}

我的问题是,如果 componentDidUpdate() 不是获取下拉数据的正确位置,我应该在哪里定义网络调用?

【问题讨论】:

  • 请提供一个完整的例子,尤其是父组件的代码。
  • @ford04,更新还好吗还是您需要更多信息?
  • 刚刚从您的代码更新中看到,这主要是一个react-table 问题。老实说,我对那个包不是很熟悉。你说,更新后每个渲染器组件(Cell)都会再次调用componentDidMount,所以它们显然是由react-table卸载和重新创建的。我modified 他们的样本之一,但无法重现您的问题。也许您提供了一个简单的代码框,以便更有可能有人可以提供帮助?

标签: reactjs react-table


【解决方案1】:

我找到了解决方案。在父组件中,我维护一个状态 shippingstatusmap。此地图的其中一列是 acutalEntity。现在在子组件中,每当用户从下拉列表中选择值时,我都会回调父组件以更新 shippingStatusMap。这个回调是我的问题。

因为现在父组件的状态发生了变化,它卸载了子组件并重新安装它。因此,它的 componentDidMount 会为所有行调用,而这些行又会调用 API。

解决方案

由于在加载整个父组件时我只需要一次下拉值,因此我可以将 API 移动到构造函数或父组件的 componentDidMount() 中。在构造函数中获取数据不是一个好主意。

所以我将这个 API 调用移到了父级中,瞧!一切都按预期进行。

更新代码:

子组件

import React from 'react';
import axios from 'axios';

export default class AbusiveEntityComponent extends React.Component {
  constructor(props, context) {
    super(props, context);
    this.state = {
        entity: this.props.entity,
      tid: this.props.tid,
      trackingDetailsId: this.props.trackingDetailsId,
      route: this.props.route
    };

    this.handleChange = this.handleChange.bind(this);
  }

  handleChange = (event) => {
    var selected = event.target.value;
    if(selected !== '' && this.state.entity !== selected) {
      if (window.confirm('Are you sure you want to select: '+ selected)) {
        axios.post('/entity/upateAbusiveEntity', {
        trackingDetailsId: this.state.trackingDetailsId,
        abusiveEntity: selected
      }).then( (response) =>{
        this.setState({entity: selected});
        this.props.updateActualEntityInShipmentData(this.state.tid, selected);
      })
      .catch(function (error) {
        console.log(error);
      });
      }
    }
  }

  render() {
    return (
      <div className="AbusiveEntityDiv">
         <select onChange={this.handleChange} value={this.state.entity===null?'':this.state.entity} 
            style={{width: 100}}>
          { this.state.route.map(value => <option key={value} value={value}>{value}</option>) }
         </select>
      </div>
    );
  }
}

父组件

import React from 'react';
import ReactTable from 'react-table';
import 'react-table/react-table.css';
import Popup from "reactjs-popup";

export default class DetailsTable extends React.Component {

  constructor(props, context) {
    super(props, context);

    this.state = {
      shipmentDataMap : { },
      selectedRow: null,
      downloadableAlerts: []
    };
    this.setState = this.setState.bind(this);
    this.handleRowClick = this.handleRowClick.bind(this);
    this.handleReassignment = this.handleReassignment.bind(this);
    this.handleStatusUpdate = this.handleStatusUpdate.bind(this);
    this.generateFilteredArr = this.generateFilteredArr.bind(this);
    this.handleDownload = this.handleDownload.bind(this);
    this.updateActualEntity = this.updateActualEntity.bind(this);
  };


// this portion was updated
  componentDidMount() {
    fetch('/shipment/all')
      .then(res => res.json())
      .then(shipmentList => {
        var tidToShipmentMap = {};
        var totalShipmentCount = shipmentList.length;
        var loadedShipmentRoute = 0;
        shipmentList.forEach(shipment => {

          axios.get('/entity/getRoute', {
            params: {
              trackingId: shipment.tid
            }
          })
          .then(response => {
            let tempRoute = [];
            let prev="";
            response.data.route.forEach(element => {
              if(prev!== "") {
                tempRoute.push(prev+"-"+element['node'])
              }
              tempRoute.push(element['node']);
              prev=element['node'];
            })

            shipment.route = [''].concat(tempRoute);
            tidToShipmentMap[shipment.tid] = shipment;
            loadedShipmentRoute++;
            if (loadedShipmentRoute === totalShipmentCount) {
              this.setState({ shipmentDataMap: tidToShipmentMap});
              console.log(tidToShipmentMap);
            }
          })
          .catch(function (error) {
            console.log(error);
          });
        });
      })
      .catch(error => console.log(error));
  };

    updateActualEntity = (trackingId, updatedEntity) => {
    let updatedRecord = this.state.shipmentDataMap[trackingId];
    updatedRecord.actualEntity = updatedEntity;
    this.setState({shipmentDataMap: this.state.shipmentDataMap});
  };

render() {
    const TableColumns = [{
        Header: 'Actions',
        id: 'actionPopupButton',
        filterable: false,
        style: {'textAlign': 'left'},
        Cell: row => (<div><ReassignPopup data={row.original} updateRowFunc={this.handleReassignment} nodeOptions={this.props.nodeOptions}/> 
                        <br/>
                        <UpdateStatusPopup data={row.original} updateRowFunc={this.handleStatusUpdate} statusOptions={this.props.statusOptions}/>
                        </div>)
      },
      {
        Header: 'Assigned Node',
        headerStyle: {'whiteSpace': 'unset'},
        accessor: 'node',
        style: {'whiteSpace': 'unset'}
      }, {
        Header: 'TID',
        headerStyle: {'whiteSpace': 'unset'},
        accessor: 'tid',
        width: 140,
        filterMethod: (filter, row) => {
                    return row[filter.id].startsWith(filter.value)
                  },
        Cell: props => <a href={`https://eagleeye-eu.amazon.com/search?type=Scannable&display=leg&value=${props.value}`} target="_blank" rel="noopener noreferrer">{props.value}</a>
      },
      {
        Header: 'Predicted Entity',
        headerStyle: {'whiteSpace': 'unset'},
        filterable: false,
        accessor: 'predictedEntity',
        style: {'whiteSpace': 'unset'},
      },
      {
        Header: 'Feedback',
        headerStyle: {'whiteSpace': 'unset'},
        filterable: false,
        accessor: 'actualEntity',
        width: 140,
        style: {'whiteSpace': 'unset', overflow: 'visible'},
        Cell: row => (<div><AbusiveEntityComponent entity={row.original.actualEntity}
                  tid={row.original.tid} trackingDetailsId={row.original.trackingDetailsId}
                  updateActualEntityInShipmentData={this.updateActualEntity}/></div>)
      }

      return <div> 
    <CSVLink data={this.state.downloadableAlerts} filename="ShipmentAlerts.csv" className="hidden" ref={(r) => this.csvLink = r} target="_blank"/>
    <ReactTable
      ref={(r)=>this.reactTable=r}
      className='-striped -highlight'
      filterable
      data={Object.values(this.state.shipmentDataMap)}
      //resolveData={data => data.map(row => row)}
      columns={TableColumns}
      //filtered={this.state.filtered}
      filtered={this.generateFilteredArr(this.props.filterMap, this.props.searchParams)}
      /*onFilteredChange={(filtered, column, value) => {
        this.onFilteredChangeCustom(value, column.id || column.accessor);
      }}*/
      defaultFilterMethod={(filter, row, column) => {
            const id = filter.pivotId || filter.id;
            if (typeof filter.value === "object") {
              return row[id] !== undefined
                ? filter.value.indexOf(row[id].toString()) > -1
                : true;
            } else {
              return row[id] !== undefined
                ? String(row[id]).indexOf(filter.value) > -1
                : true;
            }
          }}

      defaultPageSize={10}
      //pageSize={10}
      previousText='Previous Page'
      nextText='Next Page'
      noDataText='No intervention alerts found'
      style={{
            fontSize: "12px",
            height: "67.4vh" // Using fixed pixels/limited height will force the table body to overflow and scroll
          }}
      getTheadFilterProps={() => {return {style: {display: "none" }}}}
      getTbodyProps={() => {return {style: {overflowX: "hidden" }}}} //For preventing extra scrollbar in Firefox/Safari
      /*
      getTrProps={(state, rowInfo) => {
        if (rowInfo && rowInfo.row) {
          return {
            onClick: (e) => {this.handleRowClick(e, rowInfo)},
            style: {
                  //background: rowInfo.index === this.state.selectedRow ? '#00afec' : 'white',
                  color: rowInfo.index === this.state.selectedRow ? 'blue' : 'black'
                }    
          }
        } else {
          return {}
        }
      }
    } */
      />
  </div>;
  }
}

【讨论】:

    猜你喜欢
    • 2019-04-27
    • 2020-06-16
    • 2019-03-08
    • 2017-12-03
    • 2018-03-01
    • 1970-01-01
    • 2023-03-17
    • 1970-01-01
    • 2019-10-17
    相关资源
    最近更新 更多