Fix table re-renders on update or keyboard navigation (#12127)

This PR fixes the problem of full table re-render on any update or
keyboard navigation.

This was due to a recoil state subscribe in the RecordTable component, I
just moved it in the children effect components so that the Flux
dependency becomes inoffensive.

I also extracted one hook from the useRecordTable hook that we have to
refactor gradually.

Fixes https://github.com/twentyhq/core-team-issues/issues/979
Fixes https://github.com/twentyhq/core-team-issues/issues/932
This commit is contained in:
Lucas Bordeau
2025-05-19 22:44:51 +02:00
committed by GitHub
parent 85e2a2a92b
commit 4f4f3a64fd
5 changed files with 83 additions and 90 deletions

View File

@ -11,9 +11,7 @@ import { RecordTableScrollToFocusedRowEffect } from '@/object-record/record-tabl
import { RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/RecordTableClickOutsideListenerId';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { isRecordTableCellFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableCellFocusActiveComponentState';
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
import { isRecordTableRowFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableRowFocusActiveComponentState';
import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
@ -48,16 +46,6 @@ export const RecordTable = () => {
const recordTableIsEmpty =
!isRecordTableInitialLoading && allRecordIds.length === 0;
const isRecordTableCellFocusActive = useRecoilComponentValueV2(
isRecordTableCellFocusActiveComponentState,
recordTableId,
);
const isRecordTableRowFocusActive = useRecoilComponentValueV2(
isRecordTableRowFocusActiveComponentState,
recordTableId,
);
if (!isNonEmptyString(objectNameSingular)) {
return <></>;
}
@ -77,11 +65,8 @@ export const RecordTable = () => {
hasRecordGroups={hasRecordGroups}
tableBodyRef={tableBodyRef}
/>
{isRecordTableCellFocusActive && <RecordTableScrollToFocusedCellEffect />}
{isRecordTableRowFocusActive && <RecordTableScrollToFocusedRowEffect />}
<RecordTableScrollToFocusedCellEffect />
<RecordTableScrollToFocusedRowEffect />
{recordTableIsEmpty && !hasRecordGroups ? (
<RecordTableEmpty
tableBodyRef={tableBodyRef}

View File

@ -1,4 +1,5 @@
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { isRecordTableCellFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableCellFocusActiveComponentState';
import { recordTableFocusPositionComponentState } from '@/object-record/record-table/states/recordTableFocusPositionComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useEffect } from 'react';
@ -7,6 +8,11 @@ import { isDefined } from 'twenty-shared/utils';
export const RecordTableScrollToFocusedCellEffect = () => {
const { recordTableId } = useRecordTableContextOrThrow();
const isRecordTableCellFocusActive = useRecoilComponentValueV2(
isRecordTableCellFocusActiveComponentState,
recordTableId,
);
const focusPosition = useRecoilComponentValueV2(
recordTableFocusPositionComponentState,
recordTableId,
@ -14,6 +20,10 @@ export const RecordTableScrollToFocusedCellEffect = () => {
// Handle cell focus
useEffect(() => {
if (!isRecordTableCellFocusActive) {
return;
}
if (!focusPosition) {
return;
}
@ -52,7 +62,7 @@ export const RecordTableScrollToFocusedCellEffect = () => {
focusElement.style.scrollMarginBottom = '';
}
};
}, [focusPosition]);
}, [focusPosition, isRecordTableCellFocusActive]);
return null;
};

View File

@ -0,0 +1,68 @@
import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope';
import { useRecordTableMove } from '@/object-record/record-table/hooks/useRecordTableMove';
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { Key } from 'ts-key-enum';
export const useMapKeyboardToFocus = (recordTableId?: string) => {
const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope();
const { move } = useRecordTableMove(recordTableId);
useScopedHotkeys(
[Key.ArrowUp, `${Key.Shift}+${Key.Enter}`],
() => {
move('up');
},
TableHotkeyScope.TableFocus,
[move],
);
useScopedHotkeys(
Key.ArrowDown,
() => {
move('down');
},
TableHotkeyScope.TableFocus,
[move],
);
useScopedHotkeys(
[Key.ArrowUp],
() => {
setHotkeyScopeAndMemorizePreviousScope(TableHotkeyScope.TableFocus);
move('up');
},
RecordIndexHotkeyScope.RecordIndex,
[move],
);
useScopedHotkeys(
[Key.ArrowDown],
() => {
setHotkeyScopeAndMemorizePreviousScope(TableHotkeyScope.TableFocus);
move('down');
},
RecordIndexHotkeyScope.RecordIndex,
[move],
);
useScopedHotkeys(
[Key.ArrowLeft, `${Key.Shift}+${Key.Tab}`],
() => {
move('left');
},
TableHotkeyScope.TableFocus,
[move],
);
useScopedHotkeys(
[Key.ArrowRight, Key.Tab],
() => {
move('right');
},
TableHotkeyScope.TableFocus,
[move],
);
};

View File

@ -1,15 +1,11 @@
import { useRecoilCallback } from 'recoil';
import { Key } from 'ts-key-enum';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { useSetHasUserSelectedAllRows } from '@/object-record/record-table/hooks/internal/useSetAllRowSelectedState';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope';
import { ColumnDefinition } from '../types/ColumnDefinition';
import { TableHotkeyScope } from '../types/TableHotkeyScope';
import { availableTableColumnsComponentState } from '@/object-record/record-table/states/availableTableColumnsComponentState';
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
@ -20,7 +16,6 @@ import { onEntityCountChangeComponentState } from '@/object-record/record-table/
import { useRecordTableMove } from '@/object-record/record-table/hooks/useRecordTableMove';
import { onToggleColumnSortComponentState } from '@/object-record/record-table/states/onToggleColumnSortComponentState';
import { tableLastRowVisibleComponentState } from '@/object-record/record-table/states/tableLastRowVisibleComponentState';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
@ -137,68 +132,8 @@ export const useRecordTable = (props?: useRecordTableProps) => {
const setFocusPosition = useSetRecordTableFocusPosition(recordTableId);
const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope();
const { move } = useRecordTableMove(recordTableId);
const useMapKeyboardToFocus = () => {
useScopedHotkeys(
[Key.ArrowUp, `${Key.Shift}+${Key.Enter}`],
() => {
move('up');
},
TableHotkeyScope.TableFocus,
[move],
);
useScopedHotkeys(
Key.ArrowDown,
() => {
move('down');
},
TableHotkeyScope.TableFocus,
[move],
);
useScopedHotkeys(
[Key.ArrowUp],
() => {
setHotkeyScopeAndMemorizePreviousScope(TableHotkeyScope.TableFocus);
move('up');
},
RecordIndexHotkeyScope.RecordIndex,
[move],
);
useScopedHotkeys(
[Key.ArrowDown],
() => {
setHotkeyScopeAndMemorizePreviousScope(TableHotkeyScope.TableFocus);
move('down');
},
RecordIndexHotkeyScope.RecordIndex,
[move],
);
useScopedHotkeys(
[Key.ArrowLeft, `${Key.Shift}+${Key.Tab}`],
() => {
move('left');
},
TableHotkeyScope.TableFocus,
[move],
);
useScopedHotkeys(
[Key.ArrowRight, Key.Tab],
() => {
move('right');
},
TableHotkeyScope.TableFocus,
[move],
);
};
const { selectAllRows } = useSelectAllRows(recordTableId);
return {
@ -210,7 +145,6 @@ export const useRecordTable = (props?: useRecordTableProps) => {
setRowSelected,
resetTableRowSelection,
move,
useMapKeyboardToFocus,
selectAllRows,
setOnColumnsChange,
setIsRecordTableInitialLoading,

View File

@ -1,7 +1,7 @@
import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useFocusedRecordTableRow } from '@/object-record/record-table/hooks/useFocusedRecordTableRow';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { useMapKeyboardToFocus } from '@/object-record/record-table/hooks/useMapKeyboardToFocus';
import { useSetIsRecordTableFocusActive } from '@/object-record/record-table/record-table-cell/hooks/useSetIsRecordTableFocusActive';
import { isRecordTableCellFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableCellFocusActiveComponentState';
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
@ -13,10 +13,6 @@ import { Key } from 'ts-key-enum';
export const RecordTableBodyFocusKeyboardEffect = () => {
const { recordTableId } = useRecordTableContextOrThrow();
const { useMapKeyboardToFocus } = useRecordTable({
recordTableId,
});
const setHotkeyScope = useSetHotkeyScope();
const { restoreRecordTableRowFocusFromCellPosition } =
@ -29,7 +25,7 @@ export const RecordTableBodyFocusKeyboardEffect = () => {
isRecordTableCellFocusActiveComponentState,
);
useMapKeyboardToFocus();
useMapKeyboardToFocus(recordTableId);
useScopedHotkeys(
[Key.Escape],