如何在保持数据状态有序的同时保持接收数据?

How to keep receiving data while keeping data in state in order?

我正在研究这个反应 table 当用户点击 table header 它需要对 table 进行排序时排序,排序工作正常但问题是我每秒通过 SignalR 集线器接收新数据并将状态 udata 设置为新数据。当用户单击 table header 时,它会对 table 进行排序,但会再次返回到由新数据更改的新状态。并取消排序 table 回到未排序。

有什么方法可以保持排序状态并仍然接收数据?

我是新手,不胜感激

constructor() {
    super()
    this.state = {
      udata: [],
      sort: {
        column: null,
        direction: 'desc',
      },
    }
  }

  componentDidMount() {
    let connection = new signalR.HubConnectionBuilder()
      .withUrl('/signalserver')
      .build()

    connection
      .start()
      .then(function() {})
      .catch(function(err) {
        return console.error(err.toString())
      })
    connection.on(
      'APIChannel',
      function(data) {
        this.setState({udata: data})
      }.bind(this),
    )

    async function start() {
      try {
        await connection.start()
        console.log('connected')
      } catch (err) {
        console.log(err)
        setTimeout(() => start(), 5000)
      }
    }

    connection.onclose(async () => {
      await start()
    })
  }

  onSort(column) {
    return function(e) {
      let direction = this.state.sort.direction

      if (this.state.sort.column === column) {
        // Change the sort direction if the same column is sorted.
        direction = this.state.sort.direction === 'asc' ? 'desc' : 'asc'
      }

      // Sort ascending.
      const sortedData = this.state.udata.sort((a, b) => {
        if (column === 'appName') {
          // This sorts strings taking into consideration numbers in strings.
          // e.g., Account 1, Account 2, Account 10. Normal sorting would sort it Account 1, Account 10, Account 2.
          const collator = new Intl.Collator(undefined, {
            numeric: true,
            sensitivity: 'base',
          })

          return collator.compare(a.appName, b.appName)
        } else {
          return a.contractValue - b.contractValue
        }
      })

      // Reverse the order if direction is descending.
      if (direction === 'desc') {
        sortedData.reverse()
      }

      // Set the new state.
      this.setState({
        udata: sortedData,
        sort: {
          column,
          direction,
        },
      })
    }.bind(this) // Bind "this" again because the onSort function is returning another function.
  }

  renderItem(item, key) {
    const itemRows = [
      <tr onClick={clickCallback} key={'row-data-' + key}>
        <td>{item.appName}</td>
        <td>
          <h6 className="text-muted">
            <i
              className={
                'fa fa-circle text-c-' +
                (item.appState === 'STARTED' ? 'green' : 'red') +
                ' f-10 m-r-15'
              }
            />
            {item.appState}
          </h6>
        </td>
        <td>{item.spaceName}</td>
        <td>
          <h6 className="text-muted">{item.orgName}</h6>
        </td>
        <td>
          <h6 className="text-muted">
            {new Date(item.appUpdatedAt).toLocaleString()}
          </h6>
        </td>
      </tr>,
    ]

    return itemRows
  }

  render() {
    let allItemRows = []

    this.state.udata.forEach((item, key) => {
      const perItemRows = this.renderItem(item, key)
      allItemRows = allItemRows.concat(perItemRows)
    })

    return (
      <Aux>
        <Row>
          <Table hover responsive>
            <thead>
              <tr>
                <th className="sortable" onClick={this.onSort('appName')}>
                  {' '}
                  Account Name
                </th>
                <th> State</th>
                <th> Space</th>
                <th> Organization</th>
                <th className="sortable" onClick={this.onSort('appUpdatedAt')}>
                  {' '}
                  Updated At
                </th>
              </tr>
            </thead>
            <tbody> {allItemRows}</tbody>
          </Table>
        </Row>
      </Aux>
    )
  }

使用父组件执行请求并将未排序的值和排序顺序值传递给子组件。 子组件(table 组件最有可能)将根据排序顺序值显示数据。

目前每次更改状态值时都会安装您的组件

添加一个名为 componentWillReceiveProps(nextProps) 的生命周期方法,或者您也可以使用 static getDerivedStateFromProps(props, state) 在此方法内部执行排序,这样当有新数据可用时,它会自动与那里原来的那个。因此,您的所有其他代码都保持不变,新数据只是在排序中占据了应有的位置。

将函数的排序部分移至新函数:

const sortData = (data, column, direction) => {
    // Sort ascending.
      const sortedData = data.sort((a, b) => {
        if (column === 'appName') {
          const collator = new Intl.Collator(undefined, {
            numeric: true,
            sensitivity: 'base',
          })

          return collator.compare(a.appName, b.appName)
        } else {
          return a.contractValue - b.contractValue
        }
      })

      // Reverse the order if direction is descending.
      if (direction === 'desc') {
        return sortedData.reverse()
      }
      return sortedData
}

您可以在使用 newData 设置状态之前在 componentDidMount 中使用此函数,也可以在 onSort 函数中使用此函数。

onSort(column) {
    return function(e) {
      let direction = this.state.sort.direction

      if (this.state.sort.column === column) {
        // Change the sort direction if the same column is sorted.
        direction = this.state.sort.direction === 'asc' ? 'desc' : 'asc'
      }

      // Sort ascending.
      const sortedData = this.sortData(this.state.udata, column, direction)

      // Set the new state.
      this.setState({
        udata: sortedData,
        sort: {
          column,
          direction,
        },
      })
    }.bind(this) // Bind "this" again because the onSort function is returning another function.
  }

componentDidMount:

componentDidMount() {
  // Code

   connection.on(
      'APIChannel',
      function(data) {
        let sortedData = []
        if (this.state.sort.column) {
         sortedData = this.sortData(data, this.state.sort.column, 
             this.state.sort.direction)
        } else {
         sortedData = data
        }

        this.setState({udata: sortedData})
      }.bind(this),
    )

   // Rest of the code
}

编辑:

import React, { Component } from "react";
import { Row, Col, Form, Card, Table, Tab, Nav } from "react-bootstrap";
import Aux from "../../hoc/_Aux";
import * as signalR from "@aspnet/signalr";

class Dashboard extends Component {
  constructor() {
    super();
    this.state = {
      udata: [],
      sysdata: [],
      expandedRows: [],
      user: "active",
      system: "",
      data: [],
      UserFilters: {
        appState: [],
        orgName: [],
        spaceName: []
      },
      SysFilters: {
        appState: []
      },
      intervalId: 0, //Scroll on top feature
      sort: {
        column: null,
        direction: "desc"
      }
    };
  }

  sortData = (data, column, direction) => {
    // Sort ascending.
    const sortedData = data.sort((a, b) => {
      if (column === 'appName') {
        const collator = new Intl.Collator(undefined, {
          numeric: true,
          sensitivity: 'base',
        })

        return collator.compare(a.appName, b.appName)
      } else {
        return a.contractValue - b.contractValue
      }
    })

    // Reverse the order if direction is descending.
    if (direction === 'desc') {
      return sortedData.reverse()
    }
    return sortedData
  };

  componentDidMount() {
    let connection = new signalR.HubConnectionBuilder()
      .withUrl("/signalserver")
      .build();

    connection
      .start()
      .then(function () { })
      .catch(function (err) {
        return console.error(err.toString());
      });

    connection.on(
      "SBUserBrodcasting",
      function (data) {
        let sortedData = [];
        if (this.state.sort.column) {
          sortedData = this.sortData(
            data,
            this.state.sort.column,
            this.state.sort.direction
          );
        } else {
          sortedData = data;
        }
        this.setState({ udata: sortedData });
      }.bind(this)
    );

    connection.on(
      "SBSystemBrodcasting",
      function (data) {
        this.setState({ sysdata: data });
      }.bind(this)
    );

    async function start() {
      try {
        await connection.start();
        console.log("connected");
      } catch (err) {
        console.log(err);
        setTimeout(() => start(), 5000);
      }
    }

    connection.onclose(async () => {
      await start();
    });
  }

    onSort(column) {
      return function (e) {
        let direction = this.state.sort.direction;

        if (this.state.sort.column === column) {
          // Change the sort direction if the same column is sorted.
          direction = this.state.sort.direction === "asc" ? "desc" : "asc";
        }

        // Sort ascending.
        const sortedData = this.sortData(this.state.udata, column, direction);

        // Set the new state.
        this.setState({
          udata: sortedData,
          sort: {
            column,
            direction
          }
        });
      }.bind(this); // Bind "this" again because the onSort function is returning another function.
    }

  scrollStep() {
    if (window.pageYOffset === 0) {
      clearInterval(this.state.intervalId);
    }
    window.scroll(0, window.pageYOffset - this.props.scrollStepInPx);
  }

  scrollToTop() {
    let intervalId = setInterval(
      this.scrollStep.bind(this),
      this.props.delayInMs
    );
    this.setState({ intervalId: intervalId });
  }

  FilterUserArray = (array, UserFilters) => {
    let getValue = value =>
      typeof value === "string" ? value.toUpperCase() : value;

    const filterKeys = Object.keys(UserFilters);
    return array.filter(item => {
      // validates all filter criteria
      return filterKeys.every(key => {
        // ignores an empty filter
        if (!UserFilters[key].length) return true;
        return UserFilters[key].find(
          filter => getValue(filter) === getValue(item[key])
        );
      });
    });
  };

  FilterSysArray = (array, SysFilters) => {
    let getValue = value =>
      typeof value === "string" ? value.toUpperCase() : value;

    const filterKeys = Object.keys(SysFilters);
    return array.filter(item => {
      // validates all filter criteria
      return filterKeys.every(key => {
        // ignores an empty filter
        if (!SysFilters[key].length) return true;
        return SysFilters[key].find(
          filter => getValue(filter) === getValue(item[key])
        );
      });
    });
  };

  HandleRowClick(rowId) {
    const currentExpandedRows = this.state.expandedRows;
    const isRowCurrentlyExpanded = currentExpandedRows.includes(rowId);
    const newExpandedRows = isRowCurrentlyExpanded
      ? currentExpandedRows.filter(id => id !== rowId)
      : currentExpandedRows.concat(rowId);
    this.setState({ expandedRows: newExpandedRows });
  }

  SpaceRenderFilterList(item, key) {
    const itemRows = [
      <li key={"li-data-" + key}>
        <Form.Check
          custom
          type="checkbox"
          value={item}
          id={"SBSpace-" + item}
          label={item}
          onChange={this.UserAppSpaceFilter.bind(this)}
        />
      </li>
    ];
    return itemRows;
  }

  OrgRenderFilterList(item, key) {
    const itemRows = [
      <li key={"li-data-" + key}>
        <Form.Check
          custom
          type="checkbox"
          value={item}
          id={"SBOrg-" + item}
          label={item}
          onChange={this.UserAppOrgFilter.bind(this)}
        />
      </li>
    ];
    return itemRows;
  }

  RenderItem(item, key) {
    const clickCallback = () => this.HandleRowClick(key);
    const itemRows = [
      <tr onClick={clickCallback} key={"row-data-" + key}>
        <td>{item.appName}</td>
        <td>
          <h6 className="text-muted">
            <i
              className={
                "fa fa-circle text-c-" +
                (item.appState === "STARTED" ? "green" : "red") +
                " f-10 m-r-15"
              }
            />
            {item.appState}
          </h6>
        </td>
        <td>{item.spaceName}</td>
        <td>
          <h6 className="text-muted">{item.orgName}</h6>
        </td>
        <td>
          <h6 className="text-muted">
            {new Date(item.appUpdatedAt).toLocaleString()}
          </h6>
        </td>
      </tr>
    ];

    if (this.state.expandedRows.includes(key)) {
      itemRows.push(
        <tr key={"row-expanded-" + key}>
          <td colSpan="6">
            <Card className="card-event">
              <Card.Body>
                <div className="row align-items-center justify-content-center">
                  <div className="col">
                    <h5 className="m-0">Upcoming Event</h5>
                  </div>
                  <div className="col-auto">
                    <label className="label theme-bg2 text-white f-14 f-w-400 float-right">
                      34%
                    </label>
                  </div>
                </div>
                <h2 className="mt-2 f-w-300">
                  45<sub className="text-muted f-14">Competitors</sub>
                </h2>
                <h6 className="text-muted mt-3 mb-0">
                  You can participate in event{" "}
                </h6>
                <i className="fa fa-angellist text-c-purple f-50" />
              </Card.Body>
            </Card>
          </td>
        </tr>
      );
    }

    return itemRows;
  }

  onClickfn = () => {
    this.setState({ user: "active", system: "inactive" });
  };

  onClickfnsys = () => {
    this.setState({ user: "inactive", system: "active" });
  };

  UserAppStateFilter(e) {
    let index;
    // current array of options
    const options = this.state.UserFilters.appState;
    // check if the check box is checked or unchecked
    if (e.target.checked) {
      // add the numerical value of the checkbox to options array
      options.push(e.target.value);
    } else {
      // or remove the value from the unchecked checkbox from the array
      index = options.indexOf(e.target.value);
      options.splice(index, 1);
    }
    // update the state with the new array of options
    this.setState({
      UserFilters: { ...this.state.UserFilters, appState: options }
    });
  }

  UserAppSpaceFilter(e) {
    let index;
    // current array of options
    const options = this.state.UserFilters.spaceName;
    // check if the check box is checked or unchecked
    if (e.target.checked) {
      // add the numerical value of the checkbox to options array
      options.push(e.target.value);
    } else {
      // or remove the value from the unchecked checkbox from the array
      index = options.indexOf(e.target.value);
      options.splice(index, 1);
    }
    // update the state with the new array of options
    this.setState({
      UserFilters: { ...this.state.UserFilters, spaceName: options }
    });
  }

  UserAppOrgFilter(e) {
    let index;
    // current array of options
    const options = this.state.UserFilters.orgName;
    // check if the check box is checked or unchecked
    if (e.target.checked) {
      // add the numerical value of the checkbox to options array
      options.push(e.target.value);
    } else {
      // or remove the value from the unchecked checkbox from the array
      index = options.indexOf(e.target.value);
      options.splice(index, 1);
    }
    // update the state with the new array of options
    this.setState({
      UserFilters: { ...this.state.UserFilters, orgName: options }
    });
  }

  SysAppStateFilter(e) {
    let index;
    // current array of options
    const options = this.state.SysFilters.appState;
    // check if the check box is checked or unchecked
    if (e.target.checked) {
      // add the numerical value of the checkbox to options array
      options.push(e.target.value);
    } else {
      // or remove the value from the unchecked checkbox from the array
      index = options.indexOf(e.target.value);
      options.splice(index, 1);
    }
    // update the state with the new array of options
    this.setState({
      SysFilters: { ...this.state.SysFilters, appState: options }
    });
  }

  render() {
    let Spacefilterlist = [];

    Array.from(new Set(this.state.udata.map(item => item.spaceName))).forEach(
      (item, key) => {
        const perItemRows = this.SpaceRenderFilterList(item, key);
        Spacefilterlist = Spacefilterlist.concat(perItemRows);
      }
    );

    let Orgfilterlist = [];

    Array.from(new Set(this.state.udata.map(item => item.orgName))).forEach(
      (item, key) => {
        const perItemRows = this.OrgRenderFilterList(item, key);
        Orgfilterlist = Orgfilterlist.concat(perItemRows);
      }
    );

    let allItemRows = [];

    this.FilterUserArray(this.state.udata, this.state.UserFilters).forEach(
      (item, key) => {
        const perItemRows = this.RenderItem(item, key);
        allItemRows = allItemRows.concat(perItemRows);
      }
    );

    let sysallItemRows = [];

    this.FilterSysArray(this.state.sysdata, this.state.SysFilters).forEach(
      (item, key) => {
        const perItemRows = this.RenderItem(item, key);
        sysallItemRows = sysallItemRows.concat(perItemRows);
      }
    );

    return (
      <Aux>
        <Row>
          <Col sm={12}>
            <Tab.Container defaultActiveKey="user">
              <Row>
                <Col sm={2}>
                  <Nav variant="pills" className="flex-column">
                    <Nav.Item>
                      <Nav.Link eventKey="user" onClick={this.onClickfn}>
                        User
                      </Nav.Link>
                    </Nav.Item>
                    <Nav.Item>
                      <Nav.Link eventKey="system" onClick={this.onClickfnsys}>
                        System
                      </Nav.Link>
                    </Nav.Item>
                  </Nav>
                  <br />
                  <Card
                    style={{
                      display: this.state.user === "active" ? "" : "none"
                    }}
                  >
                    <Tab.Pane eventKey="user">
                      <Card.Header>
                        <Card.Title as="h5">Filters</Card.Title>
                      </Card.Header>
                      <Card.Body>
                        <h6>By State</h6>
                        <hr />
                        <ul className="list-inline m-b-0">
                          <Form.Group onReset={this.handleFormReset}>
                            <li>
                              <Form.Check
                                custom
                                type="checkbox"
                                id="checkbox1"
                                value="STARTED"
                                label="STARTED"
                                onChange={this.UserAppStateFilter.bind(this)}
                              />
                            </li>
                            <li>
                              <Form.Check
                                custom
                                type="checkbox"
                                id="checkbox2"
                                value="STOPPED"
                                label="STOPPED"
                                onChange={this.UserAppStateFilter.bind(this)}
                              />
                            </li>
                          </Form.Group>
                        </ul>
                        <h6>By Space</h6>
                        <hr />
                        <ul className="list-inline m-b-0">
                          <Form.Group>{Spacefilterlist}</Form.Group>
                        </ul>
                        <h6>By Organization</h6>
                        <hr />
                        <ul className="list-inline m-b-0">
                          <Form.Group>{Orgfilterlist}</Form.Group>
                        </ul>
                      </Card.Body>
                    </Tab.Pane>
                  </Card>
                  <Card>
                    <Tab.Pane
                      eventKey="system"
                      style={{
                        display: this.state.system === "active" ? "" : "none"
                      }}
                    >
                      <Card.Header>
                        <Card.Title as="h5">Filters</Card.Title>
                      </Card.Header>
                      <Card.Body>
                        <h6>By State</h6>
                        <hr />
                        <ul className="list-inline m-b-0">
                          <Form.Group>
                            <li>
                              <Form.Check
                                custom
                                type="checkbox"
                                id="chec1"
                                value="STARTED"
                                label="STARTED"
                                onChange={this.SysAppStateFilter.bind(this)}
                              />
                            </li>
                            <li>
                              <Form.Check
                                custom
                                type="checkbox"
                                id="chec2"
                                value="STOPPED"
                                label="STOPPED"
                                onChange={this.SysAppStateFilter.bind(this)}
                              />
                            </li>
                          </Form.Group>
                        </ul>
                      </Card.Body>
                    </Tab.Pane>
                  </Card>
                </Col>
                <Col sm={10}>
                  <Tab.Content>
                    <Tab.Pane eventKey="user">
                      <Table hover responsive>
                        <thead>
                          <tr>
                            <th
                              className="sortable"
                              onClick={this.onSort("appName")}
                            >
                              Account Name
                            </th>
                            <th>State</th>
                            <th>Space</th>
                            <th>Organization</th>
                            <th
                              className="sortable"
                              onClick={this.onSort("appUpdatedAt")}
                            >
                              Updated At
                            </th>
                          </tr>
                        </thead>
                        <tbody>{allItemRows}</tbody>
                      </Table>
                    </Tab.Pane>
                    <Tab.Pane eventKey="system">
                      <Table hover responsive>
                        <thead>
                          <tr>
                            <th>App Name</th>
                            <th>State</th>
                            <th>Space</th>
                            <th>Organization</th>
                            <th>Updated At</th>
                          </tr>
                        </thead>
                        <tbody>{sysallItemRows}</tbody>
                      </Table>
                    </Tab.Pane>
                  </Tab.Content>
                </Col>
              </Row>
            </Tab.Container>
          </Col>
          <button
            id="myBtn"
            title="Back to top"
            className="scroll"
            onClick={() => {
              this.scrollToTop();
            }}
          >
            <span className="feather icon-chevron-up" />
          </button>
        </Row>
      </Aux>
    );
  }
}

export default Dashboard;