Skip to content

Commit

Permalink
feat(useTable): selection manager to avoid calling multiple hooks (#2…
Browse files Browse the repository at this point in the history
…4377)

* feat(useTable): selection manager to avoid calling multiple hooks

Implements a `createSelectionManager` function to manage selection state
independently of react and avoids calling state hooks twice on each
render

* changefile

* pr suggestions

* add node env test

* deSelect to deselect

* update md
  • Loading branch information
ling1726 authored Aug 17, 2022
1 parent 7b46e96 commit 02a6a76
Show file tree
Hide file tree
Showing 14 changed files with 554 additions and 516 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "refactor(useTable): selection manager to avoid calling multiple hooks",
"packageName": "@fluentui/react-table",
"email": "[email protected]",
"dependentChangeType": "patch"
}
10 changes: 5 additions & 5 deletions packages/react-components/react-table/etc/react-table.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,14 @@ export interface RowState<TItem> {
// @public (undocumented)
export interface SelectionState {
allRowsSelected: boolean;
clearSelection: () => void;
deSelectRow: (rowId: RowId) => void;
clearRows: () => void;
deselectRow: (rowId: RowId) => void;
isRowSelected: (rowId: RowId) => boolean;
selectedRows: RowId[];
selectRow: (rowId: RowId) => void;
someRowsSelected: boolean;
toggleRowSelect: (rowId: RowId) => void;
toggleSelectAllRows: () => void;
toggleAllRows: () => void;
toggleRow: (rowId: RowId) => void;
}

// @public (undocumented)
Expand Down Expand Up @@ -349,7 +349,7 @@ export interface UseTableOptions<TItem, TRowState extends RowState<TItem> = RowS
// (undocumented)
rowEnhancer?: RowEnhancer<TItem, TRowState>;
// (undocumented)
selectionMode?: 'single' | 'multiselect';
selectionMode?: SelectionMode_2;
}

// @public
Expand Down
113 changes: 113 additions & 0 deletions packages/react-components/react-table/src/hooks/selectionManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { SelectionMode } from './types';

type OnSelectionChangeCallback = (selectedItems: Set<SelectionItemId>) => void;

export interface SelectionManager {
toggleItem(id: SelectionItemId): void;
selectItem(id: SelectionItemId): void;
deselectItem(id: SelectionItemId): void;
clearItems(): void;
isSelected(id: SelectionItemId): boolean;
toggleAllItems(itemIds: SelectionItemId[]): void;
}

export type SelectionItemId = string | number;

export function createSelectionManager(
mode: SelectionMode,
onSelectionChange: OnSelectionChangeCallback = () => undefined,
): SelectionManager {
const managerFactory = mode === 'multiselect' ? createMultipleSelectionManager : createSingleSelectionManager;

return managerFactory(onSelectionChange);
}

function createMultipleSelectionManager(onSelectionChange: OnSelectionChangeCallback): SelectionManager {
const selectedItems = new Set<SelectionItemId>();
const toggleAllItems = (itemIds: SelectionItemId[]) => {
const allItemsSelected = itemIds.every(itemId => selectedItems.has(itemId));

if (allItemsSelected) {
selectedItems.clear();
} else {
itemIds.forEach(itemId => selectedItems.add(itemId));
}

onSelectionChange(new Set(selectedItems));
};

const toggleItem = (itemId: SelectionItemId) => {
if (selectedItems.has(itemId)) {
selectedItems.delete(itemId);
} else {
selectedItems.add(itemId);
}

onSelectionChange(new Set(selectedItems));
};

const selectItem = (itemId: SelectionItemId) => {
selectedItems.add(itemId);
onSelectionChange(new Set(selectedItems));
};

const deselectItem = (itemId: SelectionItemId) => {
selectedItems.delete(itemId);
onSelectionChange(new Set(selectedItems));
};

const clearItems = () => {
selectedItems.clear();
onSelectionChange(new Set(selectedItems));
};

const isSelected = (itemId: SelectionItemId) => {
return selectedItems.has(itemId);
};

return {
toggleItem,
selectItem,
deselectItem,
clearItems,
isSelected,
toggleAllItems,
};
}

function createSingleSelectionManager(onSelectionChange: OnSelectionChangeCallback): SelectionManager {
let selectedItem: SelectionItemId | undefined = undefined;
const toggleItem = (itemId: SelectionItemId) => {
selectedItem = itemId;
onSelectionChange(new Set([selectedItem]));
};

const clearItems = () => {
selectedItem = undefined;
onSelectionChange(new Set<SelectionItemId>());
};

const isSelected = (itemId: SelectionItemId) => {
return selectedItem === itemId;
};

const selectItem = (itemId: SelectionItemId) => {
selectedItem = itemId;
onSelectionChange(new Set([selectedItem]));
};

return {
deselectItem: clearItems,
selectItem,
toggleAllItems: () => {
if (process.env.NODE_ENV !== 'production') {
throw new Error('[react-table]: `toggleAllItems` should not be used in single selection mode');
}

return undefined;
},
toggleItem,
clearItems,
isSelected,
};
}
19 changes: 10 additions & 9 deletions packages/react-components/react-table/src/hooks/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { SortDirection } from '../components/Table/Table.types';
export type RowId = string | number;
export type ColumnId = string | number;
export type GetRowIdInternal<TItem> = (rowId: TItem, index: number) => RowId;
export type SelectionMode = 'single' | 'multiselect';

export interface ColumnDefinition<TItem> {
columnId: ColumnId;
Expand All @@ -29,17 +30,17 @@ export interface SortStateInternal<TItem> {
export interface UseTableOptions<TItem, TRowState extends RowState<TItem> = RowState<TItem>> {
columns: ColumnDefinition<TItem>[];
items: TItem[];
selectionMode?: 'single' | 'multiselect';
selectionMode?: SelectionMode;
getRowId?: (item: TItem) => RowId;
rowEnhancer?: RowEnhancer<TItem, TRowState>;
}

export interface SelectionStateInternal {
clearSelection: () => void;
deSelectRow: (rowId: RowId) => void;
clearRows: () => void;
deselectRow: (rowId: RowId) => void;
selectRow: (rowId: RowId) => void;
toggleSelectAllRows: () => void;
toggleRowSelect: (rowId: RowId) => void;
toggleAllRows: () => void;
toggleRow: (rowId: RowId) => void;
isRowSelected: (rowId: RowId) => boolean;
selectedRows: Set<RowId>;
allRowsSelected: boolean;
Expand Down Expand Up @@ -74,23 +75,23 @@ export interface SelectionState {
/**
* Clears all selected rows
*/
clearSelection: () => void;
clearRows: () => void;
/**
* Selects single row
*/
selectRow: (rowId: RowId) => void;
/**
* De-selects single row
*/
deSelectRow: (rowId: RowId) => void;
deselectRow: (rowId: RowId) => void;
/**
* Toggle selection of all rows
*/
toggleSelectAllRows: () => void;
toggleAllRows: () => void;
/**
* Toggle selection of single row
*/
toggleRowSelect: (rowId: RowId) => void;
toggleRow: (rowId: RowId) => void;
/**
* Collection of row ids corresponding to selected rows
*/
Expand Down
Loading

0 comments on commit 02a6a76

Please sign in to comment.