简体   繁体   中英

React: How to replace .querySelectorAll by single refs for multiple elements

I would like to sort table rows when clicking on table headers. I have the code working on a regular html/javascript snippet here . However it is not working as a React method. I think the problem is the use of the method .querySelectorAll() and .parentNode . I have already replaced the method .getElementById() with this.refs.rows by adding a ref to the <tbody> but I don't think one ref can point to multiple elements (to get all the <td> elements). I am looking for a similar way to replace both of the methods which don't seem to work. Or is it another problem entirely?


Edit: Added the entire React component as asked in the comments

import React from "react";

import { Row, Col, Table } from "reactstrap";

export class ComparePlayers extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            players: [],
        };
    }

    componentDidMount() {
        fetch("/players")
            .then((response) => response.json())
            .then((state) => {
                this.setState({ players: state });
            });
    }

    handleSort(n) {
        let rows, switching, i, x, y, shouldSwitch;
        switching = true;
        while (switching) {
            switching = false;
            rows = this.refs.rows; /* replaced document.getElementById("myTable").rows; */
            for (i = 0; i < rows.length; i++) {
                shouldSwitch = false;
                x = rows[i].querySelectorAll("td")[n]; // how to replace?
                y = rows[i + 1].querySelectorAll("td")[n]; // how to replace?
                if (!isNaN(Number(x.innerHTML))) {
                    if (Number(x.innerHTML) > Number(y.innerHTML)) {
                        shouldSwitch = true;
                        break;
                    }
                } else {
                    if (x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase()) {
                        shouldSwitch = true;
                        break;
                    }
                }
            }
            if (shouldSwitch) {
                rows[i].parentNode.insertBefore(rows[i + 1], rows[i]); // how to replace?
                switching = true;
            }
        }
    }

    render() {
        return (
            <Row>
                <Col>
                    <Table bordered>
                        <thead>
                            <tr>
                                {this.state.players.length > 0 &&
                                    Object.keys(this.state.players[0]).map((key, id) => (
                                        <th
                                            key={"header_" + key}
                                            onClick={() => this.handleSort(id)}
                                        >
                                            {key.toUpperCase()}
                                        </th>
                                    ))}
                            </tr>
                        </thead>
                        <tbody ref="rows">
                            {this.state.players.length > 0 &&
                                this.state.players.map((player, id) => {
                                    return (
                                        <tr key={"row" + id}>
                                            {Object.values(player).map((value) => (
                                                <td key={"table_value_" + value}>{value}</td>
                                            ))}
                                        </tr>
                                    );
                                })}
                        </tbody>
                    </Table>
                </Col>
            </Row>
        );
    }
}

UPDATE AND SOLUTION:

I used @MahdyAslamy 's answer and adapted to my state which is an array of objects. I used this tutorial to sort array of objects according to property values. Here is the final code:

handleSort(el) {
  const compareValues = (key) => {
    return function innerSort(a, b) {
      if (!a.hasOwnProperty(key) || !b.hasOwnProperty(key)) {
        // property doesn't exist on either object
        return 0;
      }

      const varA = typeof a[key] === "string" ? a[key].toUpperCase() : a[key];
      const varB = typeof b[key] === "string" ? b[key].toUpperCase() : b[key];

      let comparison = 0;
      if (varA > varB) {
        comparison = 1;
      } else if (varA < varB) {
        comparison = -1;
      }
      return comparison;
    };
  };
  const sorted = this.state.players.sort(compareValues(el));
  this.setState({
    players: sorted
  });
}

It's not good approach to sort table by changing tags position on dom. react suggest to use states and usual component life cycle for changing appearance.

for example:

 class Game extends React.Component { constructor(props) { super(props); this.state = { list: [5,6,8,9,6,5,4,22,4] } } render() { return ( <div className="game"> <button onClick={() => this.setState({ list: this.state.list.sort() })}>sort</button> <ul> {this.state.list.map(el => <li>{el}</li>)} </ul> </div> ); } } // ======================================== ReactDOM.render( <Game />, document.getElementById('root') );
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script> <div id="root"></div>

Only sort your list and then will show sorted list on render.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM