Je travaille sur ce tri de table de réaction lorsque l'utilisateur clique sur l'en-tête de la table dont il a besoin pour trier la table, le tri fonctionne mais le problème est que je reçois de nouvelles données toutes les secondes via le hub SignalR et qu'il définit l'état udata
aux nouvelles données. Lorsqu'un utilisateur clique sur l'en-tête du tableau, il trie le tableau mais revient à nouveau au nouvel état modifié par de nouvelles données. Et annule la table triée comme non triée.
Est-il possible de conserver l'état trié tout en continuant de recevoir des données?
Je suis nouveau pour réagir, toute aide serait appréciée
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> ) }
3 Réponses :
Utilisez un composant parent pour exécuter la requête et transmettre les valeurs non triées et la valeur d'ordre de tri à un composant enfant. Le composant enfant (le composant de table le plus probable) affichera les données en fonction de la valeur de l'ordre de tri.
Actuellement, votre composant est monté chaque fois que vous modifiez les valeurs d'état
Ajoutez une méthode de cycle de vie appelée componentWillReceiveProps (nextProps)
ou vous pouvez également utiliser static getDerivedStateFromProps (props, state)
pour effectuer le tri à l'intérieur de cette méthode, qui Lorsque de nouvelles données sont disponibles, elles seront automatiquement triées avec celles d'origine qui s'y trouvaient. par conséquent, tous vos autres codes restent les mêmes et les nouvelles données prennent juste leur place dans le tri.
Déplacez la partie tri de la fonction vers une nouvelle fonction:
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;
Vous pouvez utiliser cette fonction dans componentDidMount
avant de définir l'état avec newData et aussi dans Fonction onSort
.
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 }
componentDidMount:
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. }
EDIT: strong >
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 }
Je n'ai pas tout votre code. Ce doit être quelque chose avec un formatage. Ou vous pouvez l'ajouter à codesandbox et je peux le tester pour vous. C'est beaucoup plus facile
Le code que j'ai ajouté devrait fonctionner correctement. Prenez simplement soin des autres erreurs en premier, puis ajoutez ce code.
sans votre code cela fonctionne très bien, les seules erreurs que j'obtiens sont Ligne 52: 'sorterdData' n'est pas défini no-undef Ligne 68: 'sortData' n'est pas défini no-undef Ligne 105: 'sortData' est non défini no-undef
Ajoutez this.
à la fonction où elle est appelée.
erreur sur 'onSort' n'est pas défini no-undef
J'ai supprimé const
car il donnait l'erreur onSort
n'est pas une fonction
y a-t-il une raison pour laquelle vous avez ajouté deux fois la fonction sortData
?
vous devez enregistrer la sélection de tri dans l'état et en fonction du type de tri, vous devez trier les données dans le rendu, au lieu de définir l'état "udata: sortedData", de cette façon, lorsque vous indiquez la mise à jour de l'API, vous pouvez directement en fonction du tri rendre en les triant
Ajout d'une solution, vérifiez-la et faites-le moi savoir.