Skip to content

Commit 76e3882

Browse files
Refactored several React class components as functional components (#8750)
In this PR I converted several class components to functional components using modern React hooks. Most of the components are located in the general `frontend/javascripts/components` directory. ### Steps to test: Check that the view are still showing ### Issues: Contributes to #8747 ------ (Please delete unneeded items, merge only when none are left open) - [ ] Added changelog entry (create a `$PR_NUMBER.md` file in `unreleased_changes` or use `./tools/create-changelog-entry.py`) - [ ] Added migration guide entry if applicable (edit the same file as for the changelog) - [ ] Updated [documentation](../blob/master/docs) if applicable - [ ] Adapted [wk-libs python client](https://github.com/scalableminds/webknossos-libs/tree/master/webknossos/webknossos/client) if relevant API parts change - [ ] Removed dev-only changes like prints and application.conf edits - [ ] Considered [common edge cases](../blob/master/.github/common_edge_cases.md) - [ ] Needs datastore update after deployment --------- Co-authored-by: MichaelBuessemeyer <39529669+MichaelBuessemeyer@users.noreply.github.com>
1 parent 254d094 commit 76e3882

File tree

7 files changed

+237
-281
lines changed

7 files changed

+237
-281
lines changed
Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,20 @@
11
import window from "libs/window";
22
import React from "react";
3-
import type { EmptyObject } from "types/globals";
4-
export default class DisableGenericDnd extends React.Component<EmptyObject> {
5-
componentDidMount() {
6-
window.addEventListener("dragover", this.preventDefault, false);
7-
window.addEventListener("drop", this.preventDefault, false);
8-
}
93

10-
componentWillUnmount() {
11-
window.removeEventListener("dragover", this.preventDefault);
12-
window.removeEventListener("drop", this.preventDefault);
13-
}
4+
const preventDefault = (e: Event) => {
5+
e.preventDefault();
6+
};
147

15-
preventDefault = (e: Event) => {
16-
e.preventDefault();
17-
};
8+
export default function DisableGenericDnd() {
9+
React.useEffect(() => {
10+
window.addEventListener("dragover", preventDefault, false);
11+
window.addEventListener("drop", preventDefault, false);
1812

19-
render() {
20-
return null;
21-
}
13+
return () => {
14+
window.removeEventListener("dragover", preventDefault);
15+
window.removeEventListener("drop", preventDefault);
16+
};
17+
}, []);
18+
19+
return null;
2220
}
Lines changed: 67 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
import { Button, Table, type TableProps } from "antd";
2-
import type { ColumnsType, GetRowKey } from "antd/lib/table/interface";
3-
import React from "react";
2+
import type { ColumnsType, ExpandableConfig, GetRowKey } from "antd/lib/table/interface";
3+
import type React from "react";
4+
import { useEffect, useState } from "react";
45

5-
type State = {
6-
expandedRows: Array<string>;
7-
className?: string;
8-
};
96
/** This is a wrapper for large tables that have fixed columns and support expanded rows.
107
* This wrapper ensures that when rows are expanded no column is fixed as this creates rendering bugs.
118
* If you are using this wrapper, you do not need to set the class "large-table"
@@ -17,72 +14,73 @@ type OwnTableProps<RecordType = any> = TableProps<RecordType> & {
1714
columns: ColumnsType<RecordType>;
1815
};
1916

20-
const EMPTY_ARRAY: string[] = [] as const;
17+
const EMPTY_ARRAY: React.Key[] = [] as const;
2118

22-
export default class FixedExpandableTable extends React.PureComponent<OwnTableProps, State> {
23-
state: State = {
24-
expandedRows: EMPTY_ARRAY,
25-
};
19+
const getAllRowIds = (
20+
dataSource: readonly any[] | undefined,
21+
rowKey: string | number | symbol | GetRowKey<any> | undefined,
22+
) => {
23+
const canUseRowKey = typeof rowKey === "string";
24+
return dataSource != null && canUseRowKey ? dataSource.map((row) => row[rowKey]) : [];
25+
};
26+
27+
export default function FixedExpandableTable<RecordType>({
28+
className,
29+
expandable,
30+
dataSource,
31+
rowKey,
32+
columns,
33+
...restProps
34+
}: OwnTableProps<RecordType>) {
35+
const [expandedRows, setExpandedRows] = useState<React.Key[]>(EMPTY_ARRAY);
2636

27-
getAllRowIds(
28-
dataSource: readonly any[] | undefined,
29-
rowKey: string | number | symbol | GetRowKey<any> | undefined,
30-
) {
31-
const canUseRowKey = typeof rowKey === "string";
32-
return dataSource != null && canUseRowKey ? dataSource.map((row) => row[rowKey]) : [];
33-
}
37+
// biome-ignore lint/correctness/useExhaustiveDependencies: Collapse all rows when source changes
38+
useEffect(() => {
39+
setExpandedRows(EMPTY_ARRAY);
40+
}, [dataSource]);
3441

35-
componentDidUpdate(prevProps: Readonly<TableProps<any>>): void {
36-
if (prevProps.dataSource !== this.props.dataSource) {
37-
this.setState({ expandedRows: EMPTY_ARRAY });
38-
}
39-
}
42+
const areAllRowsExpanded = dataSource != null && expandedRows.length === dataSource?.length;
4043

41-
render() {
42-
const { expandedRows } = this.state;
43-
const { className, expandable, ...restProps } = this.props;
44-
const { dataSource, rowKey } = this.props;
45-
const areAllRowsExpanded =
46-
dataSource != null && this.state.expandedRows.length === dataSource?.length;
44+
const columnTitleCollapsed = (
45+
<Button
46+
className="ant-table-row-expand-icon ant-table-row-expand-icon-collapsed"
47+
title="Expand all rows"
48+
onClick={() => setExpandedRows(getAllRowIds(dataSource, rowKey))}
49+
/>
50+
);
51+
const columnTitleExpanded = (
52+
<Button
53+
className="ant-table-row-expand-icon ant-table-row-expand-icon-expanded"
54+
title="Collapse all rows"
55+
onClick={() => setExpandedRows(EMPTY_ARRAY)}
56+
/>
57+
);
58+
59+
const columnsWithAdjustedFixedProp = columns.map((column) => {
60+
const columnFixed = expandedRows.length > 0 ? false : column.fixed;
61+
return { ...column, fixed: columnFixed };
62+
});
63+
64+
const expandableProp: ExpandableConfig<RecordType> = {
65+
...expandable,
66+
expandedRowKeys: expandedRows,
67+
onExpandedRowsChange: (selectedRows: readonly React.Key[]) => {
68+
setExpandedRows(selectedRows as React.Key[]);
69+
},
70+
columnTitle: areAllRowsExpanded ? columnTitleExpanded : columnTitleCollapsed,
71+
};
4772

48-
const columnTitleCollapsed = (
49-
<Button
50-
className="ant-table-row-expand-icon ant-table-row-expand-icon-collapsed"
51-
title="Expand all rows"
52-
onClick={() => this.setState({ expandedRows: this.getAllRowIds(dataSource, rowKey) })}
53-
/>
54-
);
55-
const columnTitleExpanded = (
56-
<Button
57-
className="ant-table-row-expand-icon ant-table-row-expand-icon-expanded"
58-
title="Collapse all rows"
59-
onClick={() => this.setState({ expandedRows: EMPTY_ARRAY })}
60-
/>
61-
);
62-
const columnsWithAdjustedFixedProp: TableProps["columns"] = this.props.columns.map((column) => {
63-
const columnFixed = expandedRows.length > 0 ? false : column.fixed;
64-
return { ...column, fixed: columnFixed };
65-
});
66-
const expandableProp = {
67-
...expandable,
68-
expandedRowKeys: expandedRows,
69-
onExpandedRowsChange: (selectedRows: readonly React.Key[]) => {
70-
this.setState({
71-
expandedRows: selectedRows as string[],
72-
});
73-
},
74-
columnTitle: areAllRowsExpanded ? columnTitleExpanded : columnTitleCollapsed,
75-
};
76-
return (
77-
<Table
78-
{...restProps}
79-
expandable={expandableProp}
80-
scroll={{
81-
x: "max-content",
82-
}}
83-
className={`large-table ${className}`}
84-
columns={columnsWithAdjustedFixedProp}
85-
/>
86-
);
87-
}
73+
return (
74+
<Table
75+
{...restProps}
76+
dataSource={dataSource}
77+
rowKey={rowKey}
78+
expandable={expandableProp}
79+
scroll={{
80+
x: "max-content",
81+
}}
82+
className={`large-table ${className}`}
83+
columns={columnsWithAdjustedFixedProp}
84+
/>
85+
);
8886
}
Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,28 @@
1-
import * as React from "react";
1+
import type React from "react";
2+
import { useState } from "react";
3+
24
type Props = {
35
shouldHighlight: boolean;
46
children: React.ReactNode;
57
style?: Record<string, any>;
68
};
7-
type State = {
8-
persistedShouldHighlight: boolean;
9-
}; // This component is able to highlight a newly rendered row.
9+
10+
// This component is able to highlight a newly rendered row.
1011
// Internally, it persists the initially passed props, since that
1112
// prop can change faster than the animation is executed. Not saving
1213
// the initial prop, would abort the animation too early.
14+
export default function HighlightableRow({ shouldHighlight, style, ...restProps }: Props) {
15+
const [persistedShouldHighlight] = useState(shouldHighlight);
1316

14-
export default class HighlightableRow extends React.PureComponent<Props, State> {
15-
state: State = {
16-
persistedShouldHighlight: this.props.shouldHighlight,
17-
};
18-
19-
render() {
20-
const { shouldHighlight, style, ...restProps } = this.props;
21-
return (
22-
<tr
23-
{...restProps}
24-
style={{
25-
...style,
26-
animation: this.state.persistedShouldHighlight ? "highlight-background 2.0s ease" : "",
27-
}}
28-
>
29-
{this.props.children}
30-
</tr>
31-
);
32-
}
17+
return (
18+
<tr
19+
{...restProps}
20+
style={{
21+
...style,
22+
animation: persistedShouldHighlight ? "highlight-background 2.0s ease" : "",
23+
}}
24+
>
25+
{restProps.children}
26+
</tr>
27+
);
3328
}
Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,21 @@
11
import window from "libs/window";
2-
import { Component } from "react";
3-
import type { EmptyObject } from "types/globals";
2+
import { useCallback, useEffect } from "react";
3+
44
type LoopProps = {
55
interval: number;
66
onTick: (...args: Array<any>) => any;
77
};
88

9-
class Loop extends Component<LoopProps, EmptyObject> {
10-
intervalId: number | null | undefined = null;
9+
export default function Loop({ interval, onTick }: LoopProps) {
10+
const _onTick = useCallback(onTick, []);
1111

12-
componentDidMount() {
13-
this.intervalId = window.setInterval(this.props.onTick, this.props.interval);
14-
}
12+
useEffect(() => {
13+
const intervalId = window.setInterval(_onTick, interval);
1514

16-
componentWillUnmount() {
17-
if (this.intervalId != null) {
18-
window.clearInterval(this.intervalId);
19-
this.intervalId = null;
20-
}
21-
}
15+
return () => {
16+
window.clearInterval(intervalId);
17+
};
18+
}, [interval, _onTick]);
2219

23-
render() {
24-
return null;
25-
}
20+
return null;
2621
}
27-
28-
export default Loop;

0 commit comments

Comments
 (0)