Skip to content

Commit

Permalink
fix(table): fix infinite loop (#1794)
Browse files Browse the repository at this point in the history
Co-authored-by: maxin <maxin@growingio.com>
  • Loading branch information
nnmax and maxin committed Jan 14, 2022
1 parent dd5462b commit f568a49
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 70 deletions.
2 changes: 1 addition & 1 deletion src/pagination/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ interface PaginationProps {
onChange?: (
page: number,
pageSize: number,
event: React.MouseEvent<HTMLButtonElement> | React.KeyboardEvent<HTMLInputElement> | null
event: React.MouseEvent<HTMLButtonElement> | React.KeyboardEvent<HTMLInputElement>
) => void;

/**
Expand Down
7 changes: 5 additions & 2 deletions src/table/Title.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Tooltip from '../tooltip';
import FilterPopover from './FilterPopover';
import { Key, SortOrder, TitleProps } from './interface';

export const getNextSortDirection = (sortDirections: SortOrder[], current: SortOrder): SortOrder =>
const getNextSortDirection = (sortDirections: SortOrder[], current: SortOrder): SortOrder =>
current === null ? sortDirections[0] : sortDirections[sortDirections.indexOf(current) + 1];

const Title = <RecordType,>(props: TitleProps<RecordType>): React.ReactElement => {
Expand All @@ -24,7 +24,10 @@ const Title = <RecordType,>(props: TitleProps<RecordType>): React.ReactElement =
const { sortOrder: sorterOrder } = sorterState;

const handleSorterChange = (): void => {
const changedSorterState = { ...sorterState, sortOrder: getNextSortDirection(sortDirections, sorterOrder) };
const changedSorterState = {
...sorterState,
sortOrder: getNextSortDirection(sortDirections, sorterOrder ?? null),
};
onTriggerStateUpdate({ sorterState: updateSorterStates(changedSorterState) });
};
return (
Expand Down
62 changes: 32 additions & 30 deletions src/table/hook/useFilter.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useState, useEffect } from 'react';
import { useCallback, useState, useMemo } from 'react';
import { get, isUndefined, isFunction } from 'lodash';
import { ColumnsType, FilterState } from '../interface';
import { getColumnPos, getColumnKey } from '../utils';
Expand Down Expand Up @@ -72,56 +72,58 @@ const useFilter = <RecordType,>(
// record all filter states
const [filterStates, setFilterStates] = useState<FilterState<RecordType>[]>(collectFilterStates(columns, true));
const [filters, setFilters] = useState<Record<string, string[]>>({});
useEffect(() => {
const collectedFilterStates = collectFilterStates(columns, false);

setFilterStates((oldStates) => {
const active = oldStates.filter((state) => state.filteredKeys?.length > 0);
const mergedStates = useMemo(() => {
const collectedStates = collectFilterStates(columns, false);

if (active.length > 0) {
return collectedFilterStates.map((state) => {
const { key, isControlled } = state;
if (isControlled) return state;
const filteredKeysIsNotControlled = collectedStates.every(({ filteredKeys }) => filteredKeys === undefined);

const found = active.find((item) => item.key === key);

if (found && found.filteredKeys?.length > 0) {
return {
...state,
filteredKeys: found.filteredKeys,
};
}
// Return if not controlled
if (filteredKeysIsNotControlled) {
return filterStates;
}

return state;
});
}
const active = filterStates.filter((state) => state.filteredKeys?.length > 0);
if (active.length > 0) {
return collectedStates.map((state) => {
const { key, isControlled } = state;
if (isControlled) return state;

const found = active.find((item) => item.key === key);
if (found && found.filteredKeys?.length > 0) {
return {
...state,
filteredKeys: found.filteredKeys,
};
}
return state;
});
}

return collectedFilterStates;
});
}, [columns]);
return collectedStates;
}, [columns, filterStates]);

// update filter states action
const updateFilterStates = useCallback(
(filterState: FilterState<RecordType>) => {
const newFilterStates = filterState.isControlled
? filterStates
: [...filterStates.filter(({ key }) => key !== filterState.key), filterState];
const newFilters = [...filterStates.filter(({ key }) => key !== filterState.key), filterState].reduce(
const newFilters = [...mergedStates.filter(({ key }) => key !== filterState.key), filterState].reduce(
(prev, curr) => Object.assign(prev, { [curr.key]: curr.filteredKeys }),
{} as Record<string, string[]>
);

setFilterStates(newFilterStates);
setFilterStates([...mergedStates.filter(({ key }) => key !== filterState.key), filterState]);

setFilters(newFilters);
if (isFunction(onFilterChange)) {
onFilterChange(newFilters);
}

return newFilters;
},
[filterStates, onFilterChange]
[mergedStates, onFilterChange]
);

return [filterStates, updateFilterStates, getFilteredData, filters];
return [mergedStates, updateFilterStates, getFilteredData, filters];
};

export default useFilter;
8 changes: 1 addition & 7 deletions src/table/hook/usePagination.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useMemo, useCallback, useEffect } from 'react';
import React, { useMemo, useCallback } from 'react';
import { isUndefined } from 'lodash';
import Pagination, { PaginationProps } from '../../pagination';
import { ColumnType, ColumnsType, PaginationState } from '../interface';
Expand Down Expand Up @@ -37,12 +37,6 @@ const usePagination = <RecordType,>(
return total;
}, [total, data.length]);

useEffect(() => {
if (controlledCurrent * controlledPageSize > totalMemo) {
onChange?.(Math.ceil(totalMemo / controlledPageSize) || 1, controlledPageSize, null);
}
}, [controlledCurrent, controlledPageSize, totalMemo, onChange]);

const paginationData = useMemo(() => {
if (pagination === false || !controlledPageSize) {
return data;
Expand Down
72 changes: 43 additions & 29 deletions src/table/hook/useSorter.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useState, useCallback, useEffect } from 'react';
import { get, isNil, has, isFunction } from 'lodash';
import { useState, useCallback, useMemo } from 'react';
import { isNil, isFunction } from 'lodash';
import { ColumnsType, SortState } from '../interface';
import { getColumnKey, getColumnPos } from '../utils';

Expand All @@ -8,29 +8,43 @@ type UpdateSortState<RecordType> = (sortState: SortState<RecordType>) => SortSta

const collectSortStates = <RecordType,>(
columns: ColumnsType<RecordType>,
init: boolean,
position?: string
): SortState<RecordType>[] => {
const sortStates: SortState<RecordType>[] = [];
let sortStates: SortState<RecordType>[] = [];

const push = (column: ColumnsType<RecordType>[number], key: React.Key, state?: Partial<SortState<RecordType>>) => {
const { sortPriorityOrder = 0, sortDirections = ['ascend', 'descend', null], sortOrder } = column;
sortStates.push({
column,
key,
sortPriorityOrder,
sortDirections,
sortOrder,
isControlled: true,
...state,
});
};

columns.forEach((column, index) => {
const columnPosition = getColumnPos(index, position);
const columnKey = getColumnKey(column, columnPosition);
if (has(column, 'children')) {
sortStates.push(...collectSortStates(get(column, 'children'), columnPosition));
} else if (column.sorter) {
const {
sortPriorityOrder = 0,
sortDirections = ['ascend', 'descend', null],
sortOrder,
defaultSortOrder,
} = column;
sortStates.push({
column,
key: columnKey,
sortPriorityOrder,
sortDirections,
sortOrder: sortOrder || defaultSortOrder || null,
isControlled: !isNil(sortOrder),
});
const { defaultSortOrder } = column;

if ('children' in column) {
if ('sortOrder' in column) {
push(column, columnKey);
}
sortStates = [...sortStates, ...collectSortStates(column.children, init, columnPosition)];
} else if ('sorter' in column) {
if ('sortOrder' in column) {
push(column, columnKey);
} else if (init && defaultSortOrder) {
push(column, columnKey, {
sortOrder: defaultSortOrder,
isControlled: false,
});
}
}
});
return sortStates;
Expand Down Expand Up @@ -81,18 +95,18 @@ const useSorter = <RecordType,>(
onChange: OnSortChange<RecordType>
): [SortState<RecordType>[], UpdateSortState<RecordType>, SortState<RecordType>, typeof getSortedData] => {
// record all sorter states
const [sortStates, setSortStates] = useState<SortState<RecordType>[]>(collectSortStates(columns));
const [sortStates, setSortStates] = useState<SortState<RecordType>[]>(collectSortStates(columns, true));
const [_sorter, setSorter] = useState<SortState<RecordType>>({} as SortState<RecordType>);

useEffect(() => {
const collectedData = collectSortStates(columns);
const mergeStates = useMemo(() => {
const collectedData = collectSortStates(columns, false);

const allIsControlled = collectedData.every(({ isControlled }) => isControlled);

if (allIsControlled) {
setSortStates(collectedData);
if (!collectedData.length) {
return sortStates;
}
}, [columns]);

return collectedData;
}, [columns, sortStates]);

// update sorter states action
const updateSorterStates = useCallback(
Expand Down Expand Up @@ -126,7 +140,7 @@ const useSorter = <RecordType,>(
[onChange]
);

return [sortStates, updateSorterStates, _sorter, getSortedData];
return [mergeStates, updateSorterStates, _sorter, getSortedData];
};

export default useSorter;
2 changes: 1 addition & 1 deletion src/table/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ interface ColumnGroupType<RecordType> extends Omit<ColumnType<RecordType>, 'data
}

interface SortState<RecordType>
extends Required<Pick<ColumnType<RecordType>, 'sortPriorityOrder' | 'sortDirections' | 'sortOrder'>> {
extends Pick<ColumnType<RecordType>, 'sortPriorityOrder' | 'sortDirections' | 'sortOrder'> {
column: ColumnType<RecordType>;
key: Key;
isControlled: boolean;
Expand Down

1 comment on commit f568a49

@vercel
Copy link

@vercel vercel bot commented on f568a49 Jan 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.