Skip to content
This repository was archived by the owner on Oct 19, 2021. It is now read-only.

Commit fc429c4

Browse files
authored
fix(DataTable): update state properly in componentWillReceiveProps (#754)
* fix(DataTable): update state properly in componentWillReceiveProps * fix(DataTable): derive next state from previous state in cWRP * fix(DataTable): add support for maintaining state between prop changes * chore(DataTable): remove markup from storybook example * test(DataTable): add row id order assertion
1 parent 005a5dd commit fc429c4

File tree

8 files changed

+491
-117
lines changed

8 files changed

+491
-117
lines changed

src/components/DataTable/DataTable-story.js

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,4 +458,170 @@ storiesOf('DataTable', module)
458458
)}
459459
/>
460460
)
461+
)
462+
.addWithInfo(
463+
'with dynamic rows',
464+
`
465+
Showcases DataTable behavior when rows are added to the component
466+
dynamically.
467+
`,
468+
() => {
469+
const insertInRandomPosition = (array, element) => {
470+
const index = Math.floor(Math.random() * (array.length + 1));
471+
return [...array.slice(0, index), element, ...array.slice(index)];
472+
};
473+
474+
class DynamicRows extends React.Component {
475+
state = {
476+
rows: initialRows,
477+
headers: headers,
478+
id: 0,
479+
};
480+
481+
handleOnHeaderAdd = () => {
482+
const length = this.state.headers.length;
483+
const header = {
484+
key: `header_${length}`,
485+
header: `Header ${length}`,
486+
};
487+
488+
this.setState(state => {
489+
const rows = state.rows.map(row => {
490+
return {
491+
...row,
492+
[header.key]: header.header,
493+
};
494+
});
495+
return {
496+
rows,
497+
headers: state.headers.concat(header),
498+
};
499+
});
500+
};
501+
502+
handleOnRowAdd = () => {
503+
this.setState(state => {
504+
const { id: _id, rows } = state;
505+
const id = _id + 1;
506+
const row = {
507+
id: '' + id,
508+
name: `New Row ${id}`,
509+
protocol: 'HTTP',
510+
port: id * 100,
511+
rule: id % 2 === 0 ? 'Round robin' : 'DNS delegation',
512+
attached_groups: `Row ${id}'s VM Groups`,
513+
status: 'Starting',
514+
};
515+
516+
state.headers
517+
.filter(header => row[header.key] === undefined)
518+
.forEach(header => {
519+
row[header.key] = header.header;
520+
});
521+
522+
return {
523+
id,
524+
rows: insertInRandomPosition(rows, row),
525+
};
526+
});
527+
};
528+
529+
render() {
530+
return (
531+
<DataTable
532+
rows={this.state.rows}
533+
headers={this.state.headers}
534+
render={({
535+
rows,
536+
headers,
537+
getHeaderProps,
538+
getSelectionProps,
539+
getBatchActionProps,
540+
getRowProps,
541+
onInputChange,
542+
selectedRows,
543+
}) => (
544+
<TableContainer title="DataTable with dynamic rows">
545+
<Button small onClick={this.handleOnRowAdd}>
546+
Add new row
547+
</Button>
548+
<Button small onClick={this.handleOnHeaderAdd}>
549+
Add new header
550+
</Button>
551+
<TableToolbar>
552+
<TableBatchActions {...getBatchActionProps()}>
553+
<TableBatchAction
554+
onClick={batchActionClick(selectedRows)}>
555+
Ghost
556+
</TableBatchAction>
557+
<TableBatchAction
558+
onClick={batchActionClick(selectedRows)}>
559+
Ghost
560+
</TableBatchAction>
561+
<TableBatchAction
562+
onClick={batchActionClick(selectedRows)}>
563+
Ghost
564+
</TableBatchAction>
565+
</TableBatchActions>
566+
<TableToolbarSearch onChange={onInputChange} />
567+
<TableToolbarContent>
568+
<TableToolbarAction
569+
iconName="download"
570+
iconDescription="Download"
571+
onClick={action('TableToolbarAction - Download')}
572+
/>
573+
<TableToolbarAction
574+
iconName="edit"
575+
iconDescription="Edit"
576+
onClick={action('TableToolbarAction - Edit')}
577+
/>
578+
<TableToolbarAction
579+
iconName="settings"
580+
iconDescription="Settings"
581+
onClick={action('TableToolbarAction - Settings')}
582+
/>
583+
</TableToolbarContent>
584+
</TableToolbar>
585+
<Table>
586+
<TableHead>
587+
<TableRow>
588+
<TableExpandHeader />
589+
<TableSelectAll {...getSelectionProps()} />
590+
{headers.map(header => (
591+
<TableHeader {...getHeaderProps({ header })}>
592+
{header.header}
593+
</TableHeader>
594+
))}
595+
</TableRow>
596+
</TableHead>
597+
<TableBody>
598+
{rows.map(row => (
599+
<React.Fragment key={row.id}>
600+
<TableExpandRow {...getRowProps({ row })}>
601+
<TableSelectRow {...getSelectionProps({ row })} />
602+
{row.cells.map(cell => (
603+
<TableCell key={cell.id}>{cell.value}</TableCell>
604+
))}
605+
</TableExpandRow>
606+
{row.isExpanded && (
607+
<TableExpandedRow>
608+
<TableCell colSpan={headers.length + 3}>
609+
<h1>Expandable row content</h1>
610+
<p>Description here</p>
611+
</TableCell>
612+
</TableExpandedRow>
613+
)}
614+
</React.Fragment>
615+
))}
616+
</TableBody>
617+
</Table>
618+
</TableContainer>
619+
)}
620+
/>
621+
);
622+
}
623+
}
624+
625+
return <DynamicRows />;
626+
}
461627
);

src/components/DataTable/DataTable.js

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import PropTypes from 'prop-types';
22
import React from 'react';
3+
import isEqual from 'lodash.isequal';
34
import getDerivedStateFromProps from './state/getDerivedStateFromProps';
4-
import { getNextSortState, getCurrentSortState } from './state/sorting';
5+
import { getNextSortState } from './state/sorting';
56
import denormalize from './tools/denormalize';
67
import { composeEventHandlers } from './tools/events';
78
import { defaultFilterRows } from './tools/filter';
@@ -107,19 +108,20 @@ export default class DataTable extends React.Component {
107108
}
108109

109110
componentWillReceiveProps(nextProps) {
110-
const nextState = getDerivedStateFromProps(nextProps, this.state);
111-
112-
// Preserve the sorted order by re-sorting the data
113-
if (
114-
(nextState && nextState.sortHeaderKey !== this.state.sortHeaderKey) ||
115-
nextState.sortDirection !== this.state.sortDirection
116-
) {
117-
this.setState({
118-
...nextState,
119-
...getCurrentSortState(this.props, this.state, {
120-
key: this.state.sortHeaderKey,
121-
}),
122-
});
111+
const rowIds = this.props.rows.map(row => row.id);
112+
const nextRowIds = nextProps.rows.map(row => row.id);
113+
114+
if (!isEqual(rowIds, nextRowIds)) {
115+
this.setState(state => getDerivedStateFromProps(nextProps, state));
116+
return;
117+
}
118+
119+
const headers = this.props.headers.map(header => header.key);
120+
const nextHeaders = nextProps.headers.map(header => header.key);
121+
122+
if (!isEqual(headers, nextHeaders)) {
123+
this.setState(state => getDerivedStateFromProps(nextProps, state));
124+
return;
123125
}
124126
}
125127

0 commit comments

Comments
 (0)