один реагирующий компонент обновляет все остальные реагирующие компоненты - PullRequest
0 голосов
/ 01 октября 2019

У меня есть таблица реагирования, и один из столбцов является другим компонентом. Этот компонент является раскрывающимся списком, который получает свое значение с помощью вызова API, который я определил в componentDidMount ().

У меня есть случай, когда пользователь выбирает любое значение из раскрывающегося списка, я хочу сохранить это поле вБД. Поэтому я определил этот пост-вызов в функции handleChange раскрывающегося списка.

Проблема заключается в том, что когда я изменяю значение в одной строке, каждый другой компонент в других строках также вызывает вызовы сетевых вызовов, которыеопределенный в componentDidMount (). Поэтому componentDidMount () вызывается для всех 4 записей. Я подтвердил и на стороне сервера. Я вижу четыре запроса на получение (сейчас у меня только 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 () не является правильным местомчтобы получить данные для выпадающего списка, где я должен определить сетевой вызов?

1 Ответ

0 голосов
/ 02 октября 2019

Я нашел решение. В родительском компоненте я поддерживаю состояние shipmentstatusmap. Один из столбцов этой карты - acutalEntity. Теперь в дочернем компоненте всякий раз, когда пользователь выбирает значение из выпадающего списка, я вызываю родительский компонент, чтобы также обновить shipmentStatusMap. Этот обратный звонок был моей проблемой.

Поскольку теперь состояние родительского компонента изменяется, он размонтирует дочерний компонент и заново его монтирует. Таким образом, componentDidMount вызывается для всех строк, что, в свою очередь, вызывает API.

Решение

Поскольку я хочу, чтобы раскрывающиеся значения были только один раз при загрузке всего родительского компонентаЯ могу переместить API в конструктор или в componentDidMount () родительского. Выборка данных в конструкторе не очень хорошая идея.

Так что я переместил этот вызов API в parent и вуаля! все работает как положено.

обновленный код:

дочерний компонент

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