Skip to content

Commit

Permalink
Speed up RecordTableCell by 5x (twentyhq#5023)
Browse files Browse the repository at this point in the history
Improved table cell performances by putting all hooks from
RecordTableCell in RecordTableContext and RecordTable component, so that
each cell now only subscribes to a reference to those hooks' returned
function.

We couldn't do memoization here since the problem is not to memoize
between re-renders but to share the same function reference between
hundreds of different components, so a context it the fastest way for
this.

I had to refactor the hooks a little bit so that they take as arguments
what was previously taken from the cell's context.
  • Loading branch information
lucasbordeau authored Apr 18, 2024
1 parent 3e60c00 commit 86afc34
Show file tree
Hide file tree
Showing 19 changed files with 734 additions and 108 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { isUndefined } from '@sniptt/guards';
import { useRecoilCallback } from 'recoil';

import { recordFieldInputDraftValueComponentSelector } from '@/object-record/record-field/states/selectors/recordFieldInputDraftValueComponentSelector';
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
import { FieldInputDraftValue } from '@/object-record/record-field/types/FieldInputDraftValue';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { computeDraftValueFromFieldValue } from '@/object-record/record-field/utils/computeDraftValueFromFieldValue';
import { computeDraftValueFromString } from '@/object-record/record-field/utils/computeDraftValueFromString';
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
import { extractComponentSelector } from '@/ui/utilities/state/component-state/utils/extractComponentSelector';

export const useInitDraftValueV2 = <FieldValue>() => {
return useRecoilCallback(
({ set, snapshot }) =>
({
value,
entityId,
fieldDefinition,
}: {
value?: string;
entityId: string;
fieldDefinition: FieldDefinition<FieldMetadata>;
}) => {
const recordFieldInputScopeId = `${entityId}-${fieldDefinition?.metadata?.fieldName}-scope`;

const getDraftValueSelector = extractComponentSelector<
FieldInputDraftValue<FieldValue> | undefined
>(recordFieldInputDraftValueComponentSelector, recordFieldInputScopeId);

const recordFieldValue = snapshot
.getLoadable(
recordStoreFamilySelector<FieldValue>({
recordId: entityId,
fieldName: fieldDefinition.metadata.fieldName,
}),
)
.getValue();

if (isUndefined(value)) {
set(
getDraftValueSelector(),
computeDraftValueFromFieldValue<FieldValue>({
fieldValue: recordFieldValue,
fieldDefinition,
}),
);
} else {
set(
getDraftValueSelector(),
computeDraftValueFromString<FieldValue>({ value, fieldDefinition }),
);
}
},
[],
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,20 @@ import { RecordTableBody } from '@/object-record/record-table/components/RecordT
import { RecordTableBodyEffect } from '@/object-record/record-table/components/RecordTableBodyEffect';
import { RecordTableHeader } from '@/object-record/record-table/components/RecordTableHeader';
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
import { useHandleContainerMouseEnter } from '@/object-record/record-table/hooks/internal/useHandleContainerMouseEnter';
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
import { useRecordTableMoveFocus } from '@/object-record/record-table/hooks/useRecordTableMoveFocus';
import { useCloseRecordTableCellV2 } from '@/object-record/record-table/record-table-cell/hooks/useCloseRecordTableCellV2';
import { useMoveSoftFocusToCellOnHoverV2 } from '@/object-record/record-table/record-table-cell/hooks/useMoveSoftFocusToCellOnHoverV2';
import {
OpenTableCellArgs,
useOpenRecordTableCellV2,
} from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2';
import { useTriggerContextMenu } from '@/object-record/record-table/record-table-cell/hooks/useTriggerContextMenu';
import { useUpsertRecordV2 } from '@/object-record/record-table/record-table-cell/hooks/useUpsertRecordV2';
import { RecordTableScope } from '@/object-record/record-table/scopes/RecordTableScope';
import { MoveFocusDirection } from '@/object-record/record-table/types/MoveFocusDirection';
import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
import { MOBILE_VIEWPORT } from '@/ui/theme/constants/MobileViewport';
import { RGBA } from '@/ui/theme/constants/Rgba';
import { scrollLeftState } from '@/ui/utilities/scroll/states/scrollLeftState';
Expand Down Expand Up @@ -145,6 +157,59 @@ export const RecordTable = ({
objectNameSingular,
});

const { upsertRecord } = useUpsertRecordV2({
objectNameSingular,
});

const handleUpsertRecord = ({
persistField,
entityId,
fieldName,
}: {
persistField: () => void;
entityId: string;
fieldName: string;
}) => {
upsertRecord(persistField, entityId, fieldName, recordTableId);
};

const { openTableCell } = useOpenRecordTableCellV2(recordTableId);

const handleOpenTableCell = (args: OpenTableCellArgs) => {
openTableCell(args);
};

const { moveFocus } = useRecordTableMoveFocus(recordTableId);

const handleMoveFocus = (direction: MoveFocusDirection) => {
moveFocus(direction);
};

const { closeTableCell } = useCloseRecordTableCellV2(recordTableId);

const handleCloseTableCell = () => {
closeTableCell();
};

const { moveSoftFocusToCell } =
useMoveSoftFocusToCellOnHoverV2(recordTableId);

const handleMoveSoftFocusToCell = (cellPosition: TableCellPosition) => {
moveSoftFocusToCell(cellPosition);
};

const { triggerContextMenu } = useTriggerContextMenu({
recordTableId,
});

const handleContextMenu = (event: React.MouseEvent, recordId: string) => {
triggerContextMenu(event, recordId);
};

const { handleContainerMouseEnter } = useHandleContainerMouseEnter({
recordTableId,
});

return (
<RecordTableScope
recordTableScopeId={scopeId}
Expand All @@ -154,6 +219,13 @@ export const RecordTable = ({
<RecordTableContext.Provider
value={{
objectMetadataItem,
onUpsertRecord: handleUpsertRecord,
onOpenTableCell: handleOpenTableCell,
onMoveFocus: handleMoveFocus,
onCloseTableCell: handleCloseTableCell,
onMoveSoftFocusToCell: handleMoveSoftFocusToCell,
onContextMenu: handleContextMenu,
onCellMouseEnter: handleContainerMouseEnter,
}}
>
<StyledTable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkey
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';

export const RecordTableCellContainer = () => {
export const RecordTableCellFieldContextWrapper = () => {
const { objectMetadataItem } = useContext(RecordTableContext);
const { columnDefinition } = useContext(RecordTableCellContext);
const { recordId, pathToShowPage } = useContext(RecordTableRowContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';

import { getBasePathToShowPage } from '@/object-metadata/utils/getBasePathToShowPage';
import { RecordTableCellContainer } from '@/object-record/record-table/components/RecordTableCellContainer';
import { RecordTableCellFieldContextWrapper } from '@/object-record/record-table/components/RecordTableCellFieldContextWrapper';
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
Expand Down Expand Up @@ -67,7 +67,7 @@ export const RecordTableRow = ({ recordId, rowIndex }: RecordTableRowProps) => {
}}
key={column.fieldMetadataId}
>
<RecordTableCellContainer />
<RecordTableCellFieldContextWrapper />
</RecordTableCellContext.Provider>
) : (
<td key={column.fieldMetadataId}></td>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,28 @@
import { createContext } from 'react';

import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { HandleContainerMouseEnterArgs } from '@/object-record/record-table/hooks/internal/useHandleContainerMouseEnter';
import { OpenTableCellArgs } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2';
import { MoveFocusDirection } from '@/object-record/record-table/types/MoveFocusDirection';
import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';

type RecordTableContextProps = {
objectMetadataItem: ObjectMetadataItem;
onUpsertRecord: ({
persistField,
entityId,
fieldName,
}: {
persistField: () => void;
entityId: string;
fieldName: string;
}) => void;
onOpenTableCell: (args: OpenTableCellArgs) => void;
onMoveFocus: (direction: MoveFocusDirection) => void;
onCloseTableCell: () => void;
onMoveSoftFocusToCell: (cellPosition: TableCellPosition) => void;
onContextMenu: (event: React.MouseEvent, recordId: string) => void;
onCellMouseEnter: (args: HandleContainerMouseEnterArgs) => void;
};

export const RecordTableContext = createContext<RecordTableContextProps>(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { useRecoilCallback } from 'recoil';

import { useMoveSoftFocusToCellOnHoverV2 } from '@/object-record/record-table/record-table-cell/hooks/useMoveSoftFocusToCellOnHoverV2';
import { currentTableCellInEditModePositionComponentState } from '@/object-record/record-table/states/currentTableCellInEditModePositionComponentState';
import { isSoftFocusUsingMouseState } from '@/object-record/record-table/states/isSoftFocusUsingMouseState';
import { isTableCellInEditModeComponentFamilyState } from '@/object-record/record-table/states/isTableCellInEditModeComponentFamilyState';
import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { extractComponentFamilyState } from '@/ui/utilities/state/component-state/utils/extractComponentFamilyState';
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';

export type HandleContainerMouseEnterArgs = {
isHovered: boolean;
setIsHovered: React.Dispatch<React.SetStateAction<boolean>>;
cellPosition: TableCellPosition;
};

export const useHandleContainerMouseEnter = ({
recordTableId,
}: {
recordTableId: string;
}) => {
const tableScopeId = getScopeIdFromComponentId(recordTableId);

const { moveSoftFocusToCell } =
useMoveSoftFocusToCellOnHoverV2(recordTableId);

const handleContainerMouseEnter = useRecoilCallback(
({ snapshot, set }) =>
({
isHovered,
setIsHovered,
cellPosition,
}: HandleContainerMouseEnterArgs) => {
const currentTableCellInEditModePositionState = extractComponentState(
currentTableCellInEditModePositionComponentState,
tableScopeId,
);

const currentTableCellInEditModePosition = getSnapshotValue(
snapshot,
currentTableCellInEditModePositionState,
);

const isTableCellInEditModeFamilyState = extractComponentFamilyState(
isTableCellInEditModeComponentFamilyState,
tableScopeId,
);

const isSomeCellInEditMode = getSnapshotValue(
snapshot,
isTableCellInEditModeFamilyState(currentTableCellInEditModePosition),
);

if (!isHovered && !isSomeCellInEditMode) {
setIsHovered(true);
moveSoftFocusToCell(cellPosition);
set(isSoftFocusUsingMouseState, true);
}
},
[tableScopeId, moveSoftFocusToCell],
);

return {
handleContainerMouseEnter,
};
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useRecoilCallback } from 'recoil';

import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
import { MoveFocusDirection } from '@/object-record/record-table/types/MoveFocusDirection';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';

import { useSetSoftFocusPosition } from './internal/useSetSoftFocusPosition';
Expand Down Expand Up @@ -167,6 +168,23 @@ export const useRecordTableMoveFocus = (recordTableId?: string) => {
],
);

const moveFocus = (direction: MoveFocusDirection) => {
switch (direction) {
case 'up':
moveUp();
break;
case 'down':
moveDown();
break;
case 'left':
moveLeft();
break;
case 'right':
moveRight();
break;
}
};

return {
scopeId,
moveDown,
Expand All @@ -175,5 +193,6 @@ export const useRecordTableMoveFocus = (recordTableId?: string) => {
moveUp,
setSoftFocusPosition,
selectedRowIdsSelector,
moveFocus,
};
};
Loading

0 comments on commit 86afc34

Please sign in to comment.