chore: Table performance improvement (#20983)
## Description Several changes made to enhance the table performance: - Batch updates of meta properties to limit the number of rerenders - Removed expensive comparator operations. - Memoised components which are not susceptible to updates. - Table filter code optimisation to limit the number of times setState is triggered. Fixes #20910
This commit is contained in:
parent
425a9f2220
commit
99afdcc2c4
|
|
@ -110,7 +110,7 @@
|
|||
"lottie-web": "^5.7.4",
|
||||
"mammoth": "^1.5.1",
|
||||
"marked": "^4.0.18",
|
||||
"memoize-one": "^5.2.1",
|
||||
"memoize-one": "^6.0.0",
|
||||
"micro-memoize": "^4.0.10",
|
||||
"moment": "2.29.4",
|
||||
"moment-timezone": "^0.5.35",
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ export interface UpdateWidgetMetaPropertyPayload {
|
|||
propertyValue: unknown;
|
||||
}
|
||||
|
||||
export interface BatchUpdateWidgetMetaPropertyPayload {
|
||||
batchMetaUpdates: UpdateWidgetMetaPropertyPayload[];
|
||||
}
|
||||
export const updateWidgetMetaPropAndEval = (
|
||||
widgetId: string,
|
||||
propertyName: string,
|
||||
|
|
@ -79,6 +82,15 @@ export const triggerEvalOnMetaUpdate = () => {
|
|||
});
|
||||
};
|
||||
|
||||
export const syncBatchUpdateWidgetMetaProperties = (
|
||||
batchMetaUpdates: UpdateWidgetMetaPropertyPayload[],
|
||||
): ReduxAction<BatchUpdateWidgetMetaPropertyPayload> => {
|
||||
return {
|
||||
type: ReduxActionTypes.BATCH_UPDATE_META_PROPS,
|
||||
payload: { batchMetaUpdates },
|
||||
};
|
||||
};
|
||||
|
||||
export const syncUpdateWidgetMetaProperty = (
|
||||
widgetId: string,
|
||||
propertyName: string,
|
||||
|
|
|
|||
|
|
@ -382,6 +382,7 @@ export const ReduxActionTypes = {
|
|||
CREATE_WORKSPACE_SUCCESS: "CREATE_WORKSPACE_SUCCESS",
|
||||
ADD_USER_TO_WORKSPACE_INIT: "ADD_USER_TO_WORKSPACE_INIT",
|
||||
ADD_USER_TO_WORKSPACE_SUCCESS: "ADD_USER_TO_WORKSPACE_ERROR",
|
||||
BATCH_UPDATE_META_PROPS: "BATCH_UPDATE_META_PROPS",
|
||||
SET_META_PROP: "SET_META_PROP",
|
||||
SET_META_PROP_AND_EVAL: "SET_META_PROP_AND_EVAL",
|
||||
META_UPDATE_DEBOUNCED_EVAL: "META_UPDATE_DEBOUNCED_EVAL",
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ describe("EditorContextProvider", () => {
|
|||
"setWidgetCache",
|
||||
"updateMetaWidgetProperty",
|
||||
"syncUpdateWidgetMetaProperty",
|
||||
"syncBatchUpdateWidgetMetaProperties",
|
||||
"triggerEvalOnMetaUpdate",
|
||||
"deleteMetaWidgets",
|
||||
"deleteWidgetProperty",
|
||||
|
|
@ -69,6 +70,7 @@ describe("EditorContextProvider", () => {
|
|||
"setWidgetCache",
|
||||
"updateMetaWidgetProperty",
|
||||
"syncUpdateWidgetMetaProperty",
|
||||
"syncBatchUpdateWidgetMetaProperties",
|
||||
"triggerEvalOnMetaUpdate",
|
||||
"updateWidgetAutoHeight",
|
||||
"checkContainersForAutoHeight",
|
||||
|
|
|
|||
|
|
@ -17,8 +17,10 @@ import {
|
|||
import type { ExecuteTriggerPayload } from "constants/AppsmithActionConstants/ActionConstants";
|
||||
import type { OccupiedSpace } from "constants/CanvasEditorConstants";
|
||||
|
||||
import type { UpdateWidgetMetaPropertyPayload } from "actions/metaActions";
|
||||
import {
|
||||
resetChildrenMetaProperty,
|
||||
syncBatchUpdateWidgetMetaProperties,
|
||||
syncUpdateWidgetMetaProperty,
|
||||
triggerEvalOnMetaUpdate,
|
||||
} from "actions/metaActions";
|
||||
|
|
@ -69,6 +71,9 @@ export type EditorContextType<TCache = unknown> = {
|
|||
propertyName: string,
|
||||
propertyValue: any,
|
||||
) => void;
|
||||
syncBatchUpdateWidgetMetaProperties?: (
|
||||
batchMetaUpdates: UpdateWidgetMetaPropertyPayload[],
|
||||
) => void;
|
||||
updateWidgetAutoHeight?: (widgetId: string, height: number) => void;
|
||||
checkContainersForAutoHeight?: () => void;
|
||||
modifyMetaWidgets?: (modifications: ModifyMetaWidgetPayload) => void;
|
||||
|
|
@ -102,6 +107,7 @@ const COMMON_API_METHODS: EditorContextTypeKey[] = [
|
|||
"setWidgetCache",
|
||||
"updateMetaWidgetProperty",
|
||||
"syncUpdateWidgetMetaProperty",
|
||||
"syncBatchUpdateWidgetMetaProperties",
|
||||
"triggerEvalOnMetaUpdate",
|
||||
"updateWidgetAutoHeight",
|
||||
"checkContainersForAutoHeight",
|
||||
|
|
@ -191,6 +197,9 @@ const mapDispatchToProps = {
|
|||
propertyName: string,
|
||||
propertyValue: any,
|
||||
) => syncUpdateWidgetMetaProperty(widgetId, propertyName, propertyValue),
|
||||
syncBatchUpdateWidgetMetaProperties: (
|
||||
batchMetaUpdates: UpdateWidgetMetaPropertyPayload[],
|
||||
) => syncBatchUpdateWidgetMetaProperties(batchMetaUpdates),
|
||||
resetChildrenMetaProperty,
|
||||
disableDrag: disableDragAction,
|
||||
deleteWidgetProperty: deletePropertyAction,
|
||||
|
|
|
|||
|
|
@ -107,6 +107,7 @@ export interface LogActionPayload {
|
|||
analytics?: Record<string, any>;
|
||||
// plugin error details if any (only for plugin errors).
|
||||
pluginErrorDetails?: any;
|
||||
meta?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface Message {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { createReducer } from "utils/ReducerUtils";
|
|||
import type {
|
||||
UpdateWidgetMetaPropertyPayload,
|
||||
ResetWidgetMetaPayload,
|
||||
BatchUpdateWidgetMetaPropertyPayload,
|
||||
} from "actions/metaActions";
|
||||
|
||||
import type { ReduxAction } from "@appsmith/constants/ReduxActionConstants";
|
||||
|
|
@ -53,6 +54,20 @@ export const metaReducer = createReducer(initialState, {
|
|||
|
||||
return nextState;
|
||||
},
|
||||
[ReduxActionTypes.BATCH_UPDATE_META_PROPS]: (
|
||||
state: MetaState,
|
||||
action: ReduxAction<BatchUpdateWidgetMetaPropertyPayload>,
|
||||
) => {
|
||||
const nextState = produce(state, (draftMetaState) => {
|
||||
const { batchMetaUpdates } = action.payload;
|
||||
batchMetaUpdates.forEach(({ propertyName, propertyValue, widgetId }) => {
|
||||
set(draftMetaState, `${widgetId}.${propertyName}`, propertyValue);
|
||||
});
|
||||
return draftMetaState;
|
||||
});
|
||||
|
||||
return nextState;
|
||||
},
|
||||
[ReduxActionTypes.SET_META_PROP_AND_EVAL]: (
|
||||
state: MetaState,
|
||||
action: ReduxAction<UpdateWidgetMetaPropertyPayload>,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React from "react";
|
||||
import type { WidgetProps } from "./BaseWidget";
|
||||
import type BaseWidget from "./BaseWidget";
|
||||
import { debounce, fromPairs } from "lodash";
|
||||
import { debounce, fromPairs, isEmpty } from "lodash";
|
||||
import { EditorContext } from "components/editorComponents/EditorContextProvider";
|
||||
import AppsmithConsole from "utils/AppsmithConsole";
|
||||
import { ENTITY_TYPE } from "entities/AppsmithConsole";
|
||||
|
|
@ -10,6 +10,12 @@ import type { ExecuteTriggerPayload } from "constants/AppsmithActionConstants/Ac
|
|||
import { connect } from "react-redux";
|
||||
import { getWidgetMetaProps } from "sagas/selectors";
|
||||
import type { AppState } from "@appsmith/reducers";
|
||||
import { error } from "loglevel";
|
||||
export type pushAction = (
|
||||
propertyName: string | batchUpdateWidgetMetaPropertyType,
|
||||
propertyValue?: unknown,
|
||||
actionExecution?: DebouncedExecuteActionPayload,
|
||||
) => void;
|
||||
|
||||
export type DebouncedExecuteActionPayload = Omit<
|
||||
ExecuteTriggerPayload,
|
||||
|
|
@ -17,8 +23,15 @@ export type DebouncedExecuteActionPayload = Omit<
|
|||
> & {
|
||||
dynamicString?: string;
|
||||
};
|
||||
export type batchUpdateWidgetMetaPropertyType = {
|
||||
propertyName: string;
|
||||
propertyValue: unknown;
|
||||
actionExecution?: DebouncedExecuteActionPayload;
|
||||
}[];
|
||||
|
||||
export interface WithMeta {
|
||||
commitBatchMetaUpdates: () => void;
|
||||
pushBatchMetaUpdates: pushAction;
|
||||
updateWidgetMetaProperty: (
|
||||
propertyName: string,
|
||||
propertyValue: unknown,
|
||||
|
|
@ -36,6 +49,7 @@ function withMeta(WrappedWidget: typeof BaseWidget) {
|
|||
|
||||
initialMetaState: Record<string, unknown>;
|
||||
actionsToExecute: Record<string, DebouncedExecuteActionPayload>;
|
||||
batchMetaUpdates: batchUpdateWidgetMetaPropertyType;
|
||||
updatedProperties: Record<string, boolean>;
|
||||
constructor(props: metaHOCProps) {
|
||||
super(props);
|
||||
|
|
@ -47,6 +61,7 @@ function withMeta(WrappedWidget: typeof BaseWidget) {
|
|||
);
|
||||
this.updatedProperties = {};
|
||||
this.actionsToExecute = {};
|
||||
this.batchMetaUpdates = [];
|
||||
}
|
||||
|
||||
addPropertyForEval = (
|
||||
|
|
@ -140,6 +155,112 @@ function withMeta(WrappedWidget: typeof BaseWidget) {
|
|||
actionExecution,
|
||||
);
|
||||
};
|
||||
/**
|
||||
This function pushes meta updates that can be commited later.
|
||||
If there are multiple updates, use this function to batch those updates together.
|
||||
*/
|
||||
pushBatchMetaUpdates: pushAction = (firstArgument, ...restArgs) => {
|
||||
//if first argument is an array its a batch lets push it
|
||||
if (Array.isArray(firstArgument)) {
|
||||
this.batchMetaUpdates.push(...firstArgument);
|
||||
return;
|
||||
}
|
||||
//if first argument is a string its a propertyName arg and we are pushing a single action
|
||||
if (typeof firstArgument === "string") {
|
||||
const [propertyValue, actionExecution] = restArgs;
|
||||
this.batchMetaUpdates.push({
|
||||
propertyName: firstArgument,
|
||||
propertyValue,
|
||||
actionExecution,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
const allArgs = [firstArgument, ...restArgs];
|
||||
|
||||
error("unknown args ", allArgs);
|
||||
};
|
||||
/**
|
||||
This function commits all batched updates in one go.
|
||||
*/
|
||||
commitBatchMetaUpdates = () => {
|
||||
//ignore commit if batch array is empty
|
||||
if (!this.batchMetaUpdates || !this.batchMetaUpdates.length) return;
|
||||
|
||||
const metaUpdates = this.batchMetaUpdates.reduce(
|
||||
(acc: any, { propertyName, propertyValue }) => {
|
||||
acc[propertyName] = propertyValue;
|
||||
return acc;
|
||||
},
|
||||
{},
|
||||
);
|
||||
|
||||
AppsmithConsole.info({
|
||||
logType: LOG_TYPE.WIDGET_UPDATE,
|
||||
text: "Widget property was updated",
|
||||
source: {
|
||||
type: ENTITY_TYPE.WIDGET,
|
||||
id: this.props.widgetId,
|
||||
name: this.props.widgetName,
|
||||
},
|
||||
meta: metaUpdates,
|
||||
});
|
||||
// extract payload from updates
|
||||
const payload = [...this.batchMetaUpdates];
|
||||
//clear batch updates
|
||||
this.batchMetaUpdates = [];
|
||||
|
||||
this.handleBatchUpdateWidgetMetaProperties(payload);
|
||||
};
|
||||
getMetaPropPath = (propertyName: string | undefined) => {
|
||||
// look at this.props.__metaOptions, check for metaPropPath value
|
||||
// if they exist, then update the propertyName
|
||||
// Below code of updating metaOptions can be removed once we have ListWidget v2 where we better manage meta values of ListWidget.
|
||||
const metaOptions = this.props.__metaOptions;
|
||||
|
||||
if (!metaOptions) return;
|
||||
|
||||
return `${metaOptions.metaPropPrefix}.${this.props.widgetName}.${propertyName}[${metaOptions.index}]`;
|
||||
};
|
||||
handleBatchUpdateWidgetMetaProperties = (
|
||||
batchMetaUpdates: batchUpdateWidgetMetaPropertyType,
|
||||
) => {
|
||||
//if no updates ignore update call
|
||||
if (!batchMetaUpdates || isEmpty(batchMetaUpdates)) return;
|
||||
|
||||
const { syncBatchUpdateWidgetMetaProperties } = this.context;
|
||||
|
||||
const widgetId = this.props.metaWidgetId || this.props.widgetId;
|
||||
|
||||
if (syncBatchUpdateWidgetMetaProperties) {
|
||||
const metaOptions = this.props.__metaOptions;
|
||||
const consolidatedUpdates = batchMetaUpdates.reduce(
|
||||
(acc: any, { propertyName, propertyValue }) => {
|
||||
acc.push({ widgetId, propertyName, propertyValue });
|
||||
if (metaOptions) {
|
||||
acc.push({
|
||||
widgetId: metaOptions.widgetId,
|
||||
propertyName: this.getMetaPropPath(propertyName),
|
||||
propertyValue,
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
[],
|
||||
);
|
||||
syncBatchUpdateWidgetMetaProperties(consolidatedUpdates);
|
||||
}
|
||||
|
||||
batchMetaUpdates.forEach(({ actionExecution, propertyName }) =>
|
||||
this.addPropertyForEval(propertyName, actionExecution),
|
||||
);
|
||||
|
||||
this.setState({}, () => {
|
||||
// react batches the setState call
|
||||
// this will result in batching multiple updateWidgetMetaProperty calls.
|
||||
this.debouncedTriggerEvalOnMetaUpdate();
|
||||
});
|
||||
};
|
||||
|
||||
handleUpdateWidgetMetaProperty = (
|
||||
propertyName: string,
|
||||
|
|
@ -165,10 +286,11 @@ function withMeta(WrappedWidget: typeof BaseWidget) {
|
|||
// if they exist, then update the propertyName
|
||||
// Below code of updating metaOptions can be removed once we have ListWidget v2 where we better manage meta values of ListWidget.
|
||||
const metaOptions = this.props.__metaOptions;
|
||||
if (metaOptions) {
|
||||
const metaPropPath = this.getMetaPropPath(propertyName);
|
||||
if (metaOptions && metaPropPath) {
|
||||
syncUpdateWidgetMetaProperty(
|
||||
metaOptions.widgetId,
|
||||
`${metaOptions.metaPropPrefix}.${this.props.widgetName}.${propertyName}[${metaOptions.index}]`,
|
||||
metaPropPath,
|
||||
propertyValue,
|
||||
);
|
||||
}
|
||||
|
|
@ -194,6 +316,8 @@ function withMeta(WrappedWidget: typeof BaseWidget) {
|
|||
return (
|
||||
<WrappedWidget
|
||||
{...this.updatedProps()}
|
||||
commitBatchMetaUpdates={this.commitBatchMetaUpdates}
|
||||
pushBatchMetaUpdates={this.pushBatchMetaUpdates}
|
||||
updateWidgetMetaProperty={this.updateWidgetMetaProperty}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -27,6 +27,9 @@ function MetaWidgetContextProvider({
|
|||
const updateWidgetProperty =
|
||||
metaEditorContextProps.updateWidgetProperty ??
|
||||
editorContextProps.updateWidgetProperty;
|
||||
const syncBatchUpdateWidgetMetaProperties =
|
||||
metaEditorContextProps.syncBatchUpdateWidgetMetaProperties ??
|
||||
editorContextProps.syncBatchUpdateWidgetMetaProperties;
|
||||
const syncUpdateWidgetMetaProperty =
|
||||
metaEditorContextProps.syncUpdateWidgetMetaProperty ??
|
||||
editorContextProps.syncUpdateWidgetMetaProperty;
|
||||
|
|
@ -77,6 +80,7 @@ function MetaWidgetContextProvider({
|
|||
getWidgetCache,
|
||||
deleteMetaWidgets,
|
||||
updateMetaWidgetProperty,
|
||||
syncBatchUpdateWidgetMetaProperties,
|
||||
}),
|
||||
[
|
||||
executeAction,
|
||||
|
|
@ -93,6 +97,7 @@ function MetaWidgetContextProvider({
|
|||
getWidgetCache,
|
||||
deleteMetaWidgets,
|
||||
updateMetaWidgetProperty,
|
||||
syncBatchUpdateWidgetMetaProperties,
|
||||
],
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useEffect, useMemo, useRef } from "react";
|
||||
import { pick, reduce } from "lodash";
|
||||
import React, { useCallback, useEffect, useMemo, useRef } from "react";
|
||||
import { reduce } from "lodash";
|
||||
import type { Row as ReactTableRowType } from "react-table";
|
||||
import {
|
||||
useTable,
|
||||
|
|
@ -157,6 +157,7 @@ export type HeaderComponentProps = {
|
|||
widgetId: string;
|
||||
};
|
||||
|
||||
const emptyArr: any = [];
|
||||
export function Table(props: TableProps) {
|
||||
const isResizingColumn = React.useRef(false);
|
||||
const handleResizeColumn = (columnWidths: Record<string, number>) => {
|
||||
|
|
@ -176,17 +177,14 @@ export function Table(props: TableProps) {
|
|||
}
|
||||
props.handleResizeColumn(columnWidthMap);
|
||||
};
|
||||
const data = React.useMemo(() => props.data, [JSON.stringify(props.data)]);
|
||||
const columnString = JSON.stringify(
|
||||
pick(props, ["columns", "compactMode", "columnWidthMap"]),
|
||||
);
|
||||
const columns = React.useMemo(() => props.columns, [columnString]);
|
||||
const { columns, data, multiRowSelection, toggleAllRowSelect } = props;
|
||||
|
||||
const tableHeadercolumns = React.useMemo(
|
||||
() =>
|
||||
props.columns.filter((column: ReactTableColumnProps) => {
|
||||
columns.filter((column: ReactTableColumnProps) => {
|
||||
return column.alias !== "actions";
|
||||
}),
|
||||
[columnString],
|
||||
[columns],
|
||||
);
|
||||
/*
|
||||
For serverSidePaginationEnabled we are taking props.data.length as the page size.
|
||||
|
|
@ -212,7 +210,8 @@ export function Table(props: TableProps) {
|
|||
totalColumnsWidth,
|
||||
} = useTable(
|
||||
{
|
||||
columns: columns,
|
||||
//columns and data needs to be memoised as per useTable specs
|
||||
columns,
|
||||
data,
|
||||
defaultColumn,
|
||||
initialState: {
|
||||
|
|
@ -234,6 +233,7 @@ export function Table(props: TableProps) {
|
|||
} else {
|
||||
// We are updating column size since the drag is complete when we are changing value of isResizing from true to false
|
||||
if (isResizingColumn.current) {
|
||||
//clear timeout logic
|
||||
//update isResizingColumn in next event loop so that dragEnd event does not trigger click event.
|
||||
setTimeout(function () {
|
||||
isResizingColumn.current = false;
|
||||
|
|
@ -247,15 +247,18 @@ export function Table(props: TableProps) {
|
|||
startIndex = 0;
|
||||
endIndex = props.data.length;
|
||||
}
|
||||
const subPage = page.slice(startIndex, endIndex);
|
||||
const selectedRowIndices = props.selectedRowIndices || [];
|
||||
const subPage = useMemo(
|
||||
() => page.slice(startIndex, endIndex),
|
||||
[page, startIndex, endIndex],
|
||||
);
|
||||
const selectedRowIndices = props.selectedRowIndices || emptyArr;
|
||||
const tableSizes = TABLE_SIZES[props.compactMode || CompactModeTypes.DEFAULT];
|
||||
const tableWrapperRef = useRef<HTMLDivElement | null>(null);
|
||||
const scrollBarRef = useRef<SimpleBar | null>(null);
|
||||
const tableHeaderWrapperRef = React.createRef<HTMLDivElement>();
|
||||
const rowSelectionState = React.useMemo(() => {
|
||||
// return : 0; no row selected | 1; all row selected | 2: some rows selected
|
||||
if (!props.multiRowSelection) return null;
|
||||
if (!multiRowSelection) return null;
|
||||
const selectedRowCount = reduce(
|
||||
page,
|
||||
(count, row) => {
|
||||
|
|
@ -266,16 +269,17 @@ export function Table(props: TableProps) {
|
|||
const result =
|
||||
selectedRowCount === 0 ? 0 : selectedRowCount === page.length ? 1 : 2;
|
||||
return result;
|
||||
}, [selectedRowIndices, page]);
|
||||
const handleAllRowSelectClick = (
|
||||
e: React.MouseEvent<HTMLDivElement, MouseEvent>,
|
||||
) => {
|
||||
}, [multiRowSelection, page, selectedRowIndices]);
|
||||
const handleAllRowSelectClick = useCallback(
|
||||
(e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||
// if all / some rows are selected we remove selection on click
|
||||
// else select all rows
|
||||
props.toggleAllRowSelect(!Boolean(rowSelectionState), page);
|
||||
toggleAllRowSelect(!Boolean(rowSelectionState), page);
|
||||
// loop over subPage rows and toggleRowSelected if required
|
||||
e.stopPropagation();
|
||||
};
|
||||
},
|
||||
[page, rowSelectionState, toggleAllRowSelect],
|
||||
);
|
||||
const isHeaderVisible =
|
||||
props.isVisibleSearch ||
|
||||
props.isVisibleFilters ||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React from "react";
|
||||
import React, { memo } from "react";
|
||||
|
||||
import { CellWrapper } from "../TableStyledWrappers";
|
||||
import type { BaseCellComponentProps } from "../Constants";
|
||||
|
|
@ -18,7 +18,7 @@ export interface RenderActionProps extends BaseCellComponentProps {
|
|||
onCommandClick: (dynamicTrigger: string, onComplete: () => void) => void;
|
||||
}
|
||||
|
||||
export function ButtonCell(props: RenderActionProps) {
|
||||
function ButtonCellComponent(props: RenderActionProps) {
|
||||
const {
|
||||
allowCellWrapping,
|
||||
cellBackground,
|
||||
|
|
@ -84,3 +84,4 @@ export function ButtonCell(props: RenderActionProps) {
|
|||
</CellWrapper>
|
||||
);
|
||||
}
|
||||
export const ButtonCell = memo(ButtonCellComponent);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React from "react";
|
||||
import React, { memo } from "react";
|
||||
import type { BaseCellComponentProps, CellAlignment } from "../Constants";
|
||||
import { ALIGN_ITEMS, JUSTIFY_CONTENT } from "../Constants";
|
||||
import { CellWrapper, TooltipContentWrapper } from "../TableStyledWrappers";
|
||||
|
|
@ -59,7 +59,7 @@ type CheckboxCellProps = BaseCellComponentProps & {
|
|||
disabledCheckboxMessage: string;
|
||||
};
|
||||
|
||||
export const CheckboxCell = (props: CheckboxCellProps) => {
|
||||
const CheckboxCellComponent = (props: CheckboxCellProps) => {
|
||||
const {
|
||||
accentColor,
|
||||
borderRadius,
|
||||
|
|
@ -122,3 +122,5 @@ export const CheckboxCell = (props: CheckboxCellProps) => {
|
|||
</CheckboxCellWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export const CheckboxCell = memo(CheckboxCellComponent);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import React from "react";
|
||||
|
||||
import React, { memo } from "react";
|
||||
import type { EventType } from "constants/AppsmithActionConstants/ActionConstants";
|
||||
import type { ButtonColumnActions } from "widgets/TableWidgetV2/constants";
|
||||
import { EditableCellActions } from "widgets/TableWidgetV2/constants";
|
||||
|
|
@ -18,7 +17,7 @@ type RenderEditActionsProps = BaseCellComponentProps & {
|
|||
onDiscard: () => void;
|
||||
};
|
||||
|
||||
export function EditActionCell(props: RenderEditActionsProps) {
|
||||
function EditActionCellComponent(props: RenderEditActionsProps) {
|
||||
const {
|
||||
allowCellWrapping,
|
||||
cellBackground,
|
||||
|
|
@ -91,3 +90,4 @@ export function EditActionCell(props: RenderEditActionsProps) {
|
|||
</CellWrapper>
|
||||
);
|
||||
}
|
||||
export const EditActionCell = memo(EditActionCellComponent);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
import React, { createRef, useCallback, useEffect, useState } from "react";
|
||||
import React, {
|
||||
createRef,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState,
|
||||
memo,
|
||||
} from "react";
|
||||
import { MenuItem, Tooltip, Menu } from "@blueprintjs/core";
|
||||
import Check from "remixicon-react/CheckFillIcon";
|
||||
import ArrowDownIcon from "remixicon-react/ArrowDownSLineIcon";
|
||||
|
|
@ -145,7 +151,7 @@ type HeaderProps = {
|
|||
) => void;
|
||||
};
|
||||
|
||||
export const HeaderCell = (props: HeaderProps) => {
|
||||
const HeaderCellComponent = (props: HeaderProps) => {
|
||||
const { column, editMode, isSortable } = props;
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
|
||||
|
|
@ -335,3 +341,4 @@ export const HeaderCell = (props: HeaderProps) => {
|
|||
</div>
|
||||
);
|
||||
};
|
||||
export const HeaderCell = memo(HeaderCellComponent);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React from "react";
|
||||
import React, { memo } from "react";
|
||||
import { getDragHandlers } from "widgets/TableWidgetV2/widget/utilities";
|
||||
import { HeaderCell } from "../cellComponents/HeaderCell";
|
||||
import type { ReactTableColumnProps } from "../Constants";
|
||||
|
|
@ -143,4 +143,4 @@ const TableColumnHeader = (props: TableColumnHeaderProps) => {
|
|||
);
|
||||
};
|
||||
|
||||
export default TableColumnHeader;
|
||||
export default memo(TableColumnHeader);
|
||||
|
|
|
|||
|
|
@ -117,4 +117,4 @@ function ActionItem(props: ActionItemProps) {
|
|||
}
|
||||
}
|
||||
|
||||
export default ActionItem;
|
||||
export default React.memo(ActionItem);
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ const PageNumberInputWrapper = styled(NumericInput)<{
|
|||
|
||||
const MIN_PAGE_COUNT = 1;
|
||||
|
||||
export function PageNumberInput(props: {
|
||||
function PageNumberInputComponent(props: {
|
||||
pageNo: number;
|
||||
pageCount: number;
|
||||
updatePageNo: (pageNo: number, event?: EventType) => void;
|
||||
|
|
@ -109,3 +109,4 @@ export function PageNumberInput(props: {
|
|||
/>
|
||||
);
|
||||
}
|
||||
export const PageNumberInput = React.memo(PageNumberInputComponent);
|
||||
|
|
|
|||
|
|
@ -1,79 +0,0 @@
|
|||
/* eslint-disable @typescript-eslint/ban-types */
|
||||
// TODO(vikcy): Fix the banned types in this file
|
||||
import React from "react";
|
||||
import type { IconName } from "@blueprintjs/core";
|
||||
import { Icon } from "@blueprintjs/core";
|
||||
import styled from "styled-components";
|
||||
|
||||
const PagerContainer = styled.div`
|
||||
&&& {
|
||||
height: 49px;
|
||||
}
|
||||
`;
|
||||
function PagerIcon(props: {
|
||||
icon: IconName;
|
||||
onClick: Function;
|
||||
className: string;
|
||||
}) {
|
||||
return (
|
||||
<Icon
|
||||
className={props.className}
|
||||
icon={props.icon}
|
||||
iconSize={14}
|
||||
onClick={props.onClick as any}
|
||||
style={{
|
||||
padding: 14,
|
||||
marginTop: 5,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
interface PagerProps {
|
||||
pageNo: number;
|
||||
prevPageClick: Function;
|
||||
nextPageClick: Function;
|
||||
}
|
||||
|
||||
const PageWrapper = styled.div`
|
||||
&& {
|
||||
width: 140px;
|
||||
display: flex;
|
||||
margin: 0 auto;
|
||||
}
|
||||
`;
|
||||
|
||||
export function TablePagination(props: PagerProps) {
|
||||
return (
|
||||
<PagerContainer className={"e-control e-pager e-lib"}>
|
||||
<PageWrapper>
|
||||
<PagerIcon
|
||||
className={
|
||||
props.pageNo <= 1
|
||||
? "e-prev e-icons e-icon-prev e-prevpagedisabled e-disable"
|
||||
: "e-prev e-icons e-icon-prev e-prevpage"
|
||||
}
|
||||
icon={"chevron-left"}
|
||||
onClick={props.prevPageClick}
|
||||
/>
|
||||
<div
|
||||
className={"e-numericcontainer"}
|
||||
style={{
|
||||
marginTop: 12,
|
||||
marginLeft: 6,
|
||||
}}
|
||||
>
|
||||
<button
|
||||
className={"e-link e-numericitem e-spacing e-currentitem e-active"}
|
||||
>
|
||||
{props.pageNo}
|
||||
</button>
|
||||
</div>
|
||||
<PagerIcon
|
||||
className={"e-next e-icons e-icon-next e-nextpage"}
|
||||
icon={"chevron-right"}
|
||||
onClick={props.nextPageClick}
|
||||
/>
|
||||
</PageWrapper>
|
||||
</PagerContainer>
|
||||
);
|
||||
}
|
||||
|
|
@ -113,17 +113,25 @@ interface TableFilterProps {
|
|||
borderRadius: string;
|
||||
}
|
||||
|
||||
const defaultFilters = [{ ...DEFAULT_FILTER }];
|
||||
const getTableFilters = (filters: ReactTableFilter[] | undefined) => {
|
||||
if (!filters || filters.length === 0) {
|
||||
return defaultFilters;
|
||||
}
|
||||
return filters;
|
||||
};
|
||||
|
||||
function TableFilterPaneContent(props: TableFilterProps) {
|
||||
const [filters, updateFilters] = React.useState(
|
||||
new Array<ReactTableFilter>(),
|
||||
getTableFilters(props.filters),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const filters: ReactTableFilter[] = props.filters ? [...props.filters] : [];
|
||||
if (filters.length === 0) {
|
||||
filters.push({ ...DEFAULT_FILTER });
|
||||
const updatedFiltersState = getTableFilters(props.filters);
|
||||
//if props has been updated update the filters state
|
||||
if (updatedFiltersState !== filters) {
|
||||
updateFilters(updatedFiltersState);
|
||||
}
|
||||
updateFilters(filters);
|
||||
}, [props.filters]);
|
||||
|
||||
const addFilter = () => {
|
||||
|
|
@ -150,8 +158,8 @@ function TableFilterPaneContent(props: TableFilterProps) {
|
|||
};
|
||||
|
||||
const clearFilters = useCallback(() => {
|
||||
props.applyFilter([{ ...DEFAULT_FILTER }]);
|
||||
}, []);
|
||||
props.applyFilter(defaultFilters);
|
||||
}, [props]);
|
||||
|
||||
const columns: DropdownOption[] = props.columns
|
||||
.map((column: ReactTableColumnProps) => {
|
||||
|
|
|
|||
|
|
@ -105,5 +105,5 @@ function TableFilters(props: TableFilterProps) {
|
|||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default TableFilters;
|
||||
const TableFiltersMemoised = React.memo(TableFilters);
|
||||
export default TableFiltersMemoised;
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ export interface AddNewRowBannerType {
|
|||
disabledAddNewRowSave: boolean;
|
||||
}
|
||||
|
||||
export function AddNewRowBanner(props: AddNewRowBannerType) {
|
||||
function AddNewRowBannerComponent(props: AddNewRowBannerType) {
|
||||
const [isDiscardLoading, setIsDiscardLoading] = useState(false);
|
||||
const [isSaveLoading, setIsSaveLoading] = useState(false);
|
||||
|
||||
|
|
@ -81,3 +81,4 @@ export function AddNewRowBanner(props: AddNewRowBannerType) {
|
|||
</Container>
|
||||
);
|
||||
}
|
||||
export const AddNewRowBanner = React.memo(AddNewRowBannerComponent);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ export interface BannerPropType extends AddNewRowBannerType {
|
|||
isAddRowInProgress: boolean;
|
||||
}
|
||||
|
||||
export function Banner(props: BannerPropType) {
|
||||
function BannerComponent(props: BannerPropType) {
|
||||
return props.isAddRowInProgress ? (
|
||||
<AddNewRowBanner
|
||||
accentColor={props.accentColor}
|
||||
|
|
@ -17,3 +17,4 @@ export function Banner(props: BannerPropType) {
|
|||
/>
|
||||
) : null;
|
||||
}
|
||||
export const Banner = React.memo(BannerComponent);
|
||||
|
|
|
|||
|
|
@ -162,7 +162,8 @@ function ReactTableComponent(props: ReactTableComponentProps) {
|
|||
width,
|
||||
} = props;
|
||||
|
||||
const sortTableColumn = (columnIndex: number, asc: boolean) => {
|
||||
const sortTableColumn = useCallback(
|
||||
(columnIndex: number, asc: boolean) => {
|
||||
if (allowSorting) {
|
||||
if (columnIndex === -1) {
|
||||
_sortTableColumn("", asc);
|
||||
|
|
@ -177,21 +178,21 @@ function ReactTableComponent(props: ReactTableComponentProps) {
|
|||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
[_sortTableColumn, allowSorting, columns],
|
||||
);
|
||||
|
||||
const selectTableRow = (row: {
|
||||
original: Record<string, unknown>;
|
||||
index: number;
|
||||
}) => {
|
||||
const selectTableRow = useCallback(
|
||||
(row: { original: Record<string, unknown>; index: number }) => {
|
||||
if (allowRowSelection) {
|
||||
onRowClick(row.original, row.index);
|
||||
}
|
||||
};
|
||||
},
|
||||
[allowRowSelection, onRowClick],
|
||||
);
|
||||
|
||||
const toggleAllRowSelect = (
|
||||
isSelect: boolean,
|
||||
pageData: Row<Record<string, unknown>>[],
|
||||
) => {
|
||||
const toggleAllRowSelect = useCallback(
|
||||
(isSelect: boolean, pageData: Row<Record<string, unknown>>[]) => {
|
||||
if (allowRowSelection) {
|
||||
if (isSelect) {
|
||||
selectAllRow(pageData);
|
||||
|
|
@ -199,7 +200,9 @@ function ReactTableComponent(props: ReactTableComponentProps) {
|
|||
unSelectAllRow(pageData);
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
[allowRowSelection, selectAllRow, unSelectAllRow],
|
||||
);
|
||||
|
||||
const memoziedDisableDrag = useCallback(
|
||||
() => disableDrag(true),
|
||||
|
|
@ -310,11 +313,13 @@ export default React.memo(ReactTableComponent, (prev, next) => {
|
|||
prev.borderWidth === next.borderWidth &&
|
||||
prev.borderColor === next.borderColor &&
|
||||
prev.accentColor === next.accentColor &&
|
||||
//shallow equal possible
|
||||
equal(prev.columnWidthMap, next.columnWidthMap) &&
|
||||
equal(prev.tableData, next.tableData) &&
|
||||
//static reference
|
||||
prev.tableData === next.tableData &&
|
||||
// Using JSON stringify becuase isEqual doesnt work with functions,
|
||||
// and we are not changing the columns manually.
|
||||
JSON.stringify(prev.columns) === JSON.stringify(next.columns) &&
|
||||
prev.columns === next.columns &&
|
||||
equal(prev.editableCell, next.editableCell) &&
|
||||
prev.variant === next.variant &&
|
||||
prev.primaryColumnId === next.primaryColumnId &&
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,164 @@
|
|||
import { isBoolean, isArray, findIndex, isEqual } from "lodash";
|
||||
import type { RenderMode } from "constants/WidgetConstants";
|
||||
import { RenderModes } from "constants/WidgetConstants";
|
||||
import { StickyType } from "../../component/Constants";
|
||||
import {
|
||||
COLUMN_MIN_WIDTH,
|
||||
DEFAULT_COLUMN_WIDTH,
|
||||
DEFAULT_COLUMN_NAME,
|
||||
} from "../../constants";
|
||||
import { fetchSticky } from "../utilities";
|
||||
import type {
|
||||
ColumnProperties,
|
||||
ReactTableColumnProps,
|
||||
} from "../../component/Constants";
|
||||
import memoizeOne from "memoize-one";
|
||||
|
||||
export type getColumns = (
|
||||
renderCell: any,
|
||||
columnWidthMap: { [key: string]: number } | undefined,
|
||||
orderedTableColumns: any,
|
||||
componentWidth: number,
|
||||
primaryColumns: Record<string, ColumnProperties>,
|
||||
renderMode: RenderMode,
|
||||
widgetId: string,
|
||||
) => ReactTableColumnProps[];
|
||||
|
||||
//TODO: (Vamsi) need to unit test this function
|
||||
|
||||
export const getColumnsPureFn: getColumns = (
|
||||
renderCell,
|
||||
columnWidthMap = {},
|
||||
orderedTableColumns = [],
|
||||
componentWidth,
|
||||
primaryColumns,
|
||||
renderMode,
|
||||
widgetId,
|
||||
) => {
|
||||
let columns: ReactTableColumnProps[] = [];
|
||||
const hiddenColumns: ReactTableColumnProps[] = [];
|
||||
|
||||
let totalColumnWidth = 0;
|
||||
|
||||
if (isArray(orderedTableColumns)) {
|
||||
orderedTableColumns.forEach((column: any) => {
|
||||
const isHidden = !column.isVisible;
|
||||
|
||||
const columnData = {
|
||||
id: column.id,
|
||||
Header:
|
||||
column.hasOwnProperty("label") && typeof column.label === "string"
|
||||
? column.label
|
||||
: DEFAULT_COLUMN_NAME,
|
||||
alias: column.alias,
|
||||
accessor: (row: any) => row[column.alias],
|
||||
width: columnWidthMap[column.id] || DEFAULT_COLUMN_WIDTH,
|
||||
minWidth: COLUMN_MIN_WIDTH,
|
||||
draggable: true,
|
||||
isHidden: false,
|
||||
isAscOrder: column.isAscOrder,
|
||||
isDerived: column.isDerived,
|
||||
sticky: fetchSticky(column.id, primaryColumns, renderMode, widgetId),
|
||||
metaProperties: {
|
||||
isHidden: isHidden,
|
||||
type: column.columnType,
|
||||
format: column.outputFormat || "",
|
||||
inputFormat: column.inputFormat || "",
|
||||
},
|
||||
columnProperties: column,
|
||||
Cell: renderCell,
|
||||
};
|
||||
|
||||
const isAllCellVisible: boolean | boolean[] = column.isCellVisible;
|
||||
|
||||
/*
|
||||
* If all cells are not visible or column itself is not visible,
|
||||
* set isHidden and push it to hiddenColumns array else columns array
|
||||
*/
|
||||
if (
|
||||
(isBoolean(isAllCellVisible) && !isAllCellVisible) ||
|
||||
(isArray(isAllCellVisible) &&
|
||||
isAllCellVisible.every((visibility) => visibility === false)) ||
|
||||
isHidden
|
||||
) {
|
||||
columnData.isHidden = true;
|
||||
hiddenColumns.push(columnData);
|
||||
} else {
|
||||
totalColumnWidth += columnData.width;
|
||||
columns.push(columnData);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const lastColumnIndex = columns.length - 1;
|
||||
if (totalColumnWidth < componentWidth) {
|
||||
/*
|
||||
This "if" block is responsible for upsizing the last column width
|
||||
if there is space left in the table container towards the right
|
||||
*/
|
||||
if (columns[lastColumnIndex]) {
|
||||
const lastColumnWidth =
|
||||
columns[lastColumnIndex].width || DEFAULT_COLUMN_WIDTH;
|
||||
const remainingWidth = componentWidth - totalColumnWidth;
|
||||
// Adding the remaining width i.e. space left towards the right, to the last column width
|
||||
columns[lastColumnIndex].width = lastColumnWidth + remainingWidth;
|
||||
}
|
||||
} else if (totalColumnWidth > componentWidth) {
|
||||
/*
|
||||
This "else-if" block is responsible for downsizing the last column width
|
||||
if the last column spills over resulting in horizontal scroll
|
||||
*/
|
||||
const extraWidth = totalColumnWidth - componentWidth;
|
||||
const lastColWidth = columns[lastColumnIndex].width || DEFAULT_COLUMN_WIDTH;
|
||||
/*
|
||||
Below if condition explanation:
|
||||
Condition 1: (lastColWidth > COLUMN_MIN_WIDTH)
|
||||
We will downsize the last column only if its greater than COLUMN_MIN_WIDTH
|
||||
Condition 2: (extraWidth < lastColWidth)
|
||||
This condition checks whether the last column is the only column that is spilling over.
|
||||
If more than one columns are spilling over we won't downsize the last column
|
||||
*/
|
||||
if (lastColWidth > COLUMN_MIN_WIDTH && extraWidth < lastColWidth) {
|
||||
const availableWidthForLastColumn = lastColWidth - extraWidth;
|
||||
/*
|
||||
Below we are making sure last column width doesn't go lower than COLUMN_MIN_WIDTH again
|
||||
as availableWidthForLastColumn might go lower than COLUMN_MIN_WIDTH in some cases
|
||||
*/
|
||||
columns[lastColumnIndex].width =
|
||||
availableWidthForLastColumn < COLUMN_MIN_WIDTH
|
||||
? COLUMN_MIN_WIDTH
|
||||
: availableWidthForLastColumn;
|
||||
}
|
||||
}
|
||||
|
||||
if (hiddenColumns.length && renderMode === RenderModes.CANVAS) {
|
||||
// Get the index of the first column that is frozen to right
|
||||
const rightFrozenColumnIdx = findIndex(
|
||||
columns,
|
||||
(col) => col.sticky === StickyType.RIGHT,
|
||||
);
|
||||
if (rightFrozenColumnIdx !== -1) {
|
||||
columns.splice(rightFrozenColumnIdx, 0, ...hiddenColumns);
|
||||
} else {
|
||||
columns = columns.concat(hiddenColumns);
|
||||
}
|
||||
}
|
||||
|
||||
return columns.filter((column: ReactTableColumnProps) => !!column.id);
|
||||
};
|
||||
|
||||
// the result of this cache function is a prop for the useTable hook, this prop needs to memoised as per their docs
|
||||
// we have noticed expensive computation from the useTable if columns isnt memoised
|
||||
export const getMemoiseGetColumnsWithLocalStorageFn = () => {
|
||||
const memoisedGetColumns = memoizeOne(getColumnsPureFn);
|
||||
|
||||
return memoizeOne(
|
||||
//we are not using this parameter it is used by the memoisation comparator
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
(widgetLocalStorageState) => {
|
||||
memoisedGetColumns.clear();
|
||||
return memoisedGetColumns as getColumns;
|
||||
},
|
||||
isEqual,
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
import log from "loglevel";
|
||||
import type { MomentInput } from "moment";
|
||||
import moment from "moment";
|
||||
import _, { isNumber, isNil, isArray } from "lodash";
|
||||
import type { EditableCell } from "../../constants";
|
||||
import { ColumnTypes, DateInputFormat } from "../../constants";
|
||||
import type { ReactTableColumnProps } from "../../component/Constants";
|
||||
import memoizeOne from "memoize-one";
|
||||
import shallowEqual from "shallowequal";
|
||||
|
||||
export type tableData = Array<Record<string, unknown>>;
|
||||
|
||||
//TODO: (Vamsi) need to unit test this function
|
||||
export const transformDataPureFn = (
|
||||
tableData: Array<Record<string, unknown>>,
|
||||
columns: ReactTableColumnProps[],
|
||||
): tableData => {
|
||||
if (isArray(tableData)) {
|
||||
return tableData.map((row, rowIndex) => {
|
||||
const newRow: { [key: string]: any } = {};
|
||||
|
||||
columns.forEach((column) => {
|
||||
const { alias } = column;
|
||||
let value = row[alias];
|
||||
|
||||
if (column.metaProperties) {
|
||||
switch (column.metaProperties.type) {
|
||||
case ColumnTypes.DATE:
|
||||
let isValidDate = true;
|
||||
const outputFormat = _.isArray(column.metaProperties.format)
|
||||
? column.metaProperties.format[rowIndex]
|
||||
: column.metaProperties.format;
|
||||
let inputFormat;
|
||||
|
||||
try {
|
||||
const type = _.isArray(column.metaProperties.inputFormat)
|
||||
? column.metaProperties.inputFormat[rowIndex]
|
||||
: column.metaProperties.inputFormat;
|
||||
|
||||
if (
|
||||
type !== DateInputFormat.EPOCH &&
|
||||
type !== DateInputFormat.MILLISECONDS
|
||||
) {
|
||||
inputFormat = type;
|
||||
moment(value as MomentInput, inputFormat);
|
||||
} else if (!isNumber(value)) {
|
||||
isValidDate = false;
|
||||
}
|
||||
} catch (e) {
|
||||
isValidDate = false;
|
||||
}
|
||||
|
||||
if (isValidDate && value) {
|
||||
try {
|
||||
if (
|
||||
column.metaProperties.inputFormat ===
|
||||
DateInputFormat.MILLISECONDS
|
||||
) {
|
||||
value = Number(value);
|
||||
} else if (
|
||||
column.metaProperties.inputFormat === DateInputFormat.EPOCH
|
||||
) {
|
||||
value = 1000 * Number(value);
|
||||
}
|
||||
|
||||
newRow[alias] = moment(
|
||||
value as MomentInput,
|
||||
inputFormat,
|
||||
).format(outputFormat);
|
||||
} catch (e) {
|
||||
log.debug("Unable to parse Date:", { e });
|
||||
newRow[alias] = "";
|
||||
}
|
||||
} else if (value) {
|
||||
newRow[alias] = "Invalid Value";
|
||||
} else {
|
||||
newRow[alias] = "";
|
||||
}
|
||||
break;
|
||||
default:
|
||||
let data;
|
||||
|
||||
if (
|
||||
_.isString(value) ||
|
||||
_.isNumber(value) ||
|
||||
_.isBoolean(value)
|
||||
) {
|
||||
data = value;
|
||||
} else if (isNil(value)) {
|
||||
data = "";
|
||||
} else {
|
||||
data = JSON.stringify(value);
|
||||
}
|
||||
|
||||
newRow[alias] = data;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return newRow;
|
||||
});
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
// lazily generate the cache so that we can create several memoised instances
|
||||
const getMemoizedTransformData = () => memoizeOne(transformDataPureFn);
|
||||
|
||||
export const injectEditableCellToTableData = (
|
||||
tableData: tableData,
|
||||
editableCell: EditableCell | undefined,
|
||||
): tableData => {
|
||||
/*
|
||||
* Inject the edited cell value from the editableCell object
|
||||
*/
|
||||
if (!editableCell || !tableData.length) return tableData;
|
||||
const { column, index: updatedRowIndex, inputValue } = editableCell;
|
||||
|
||||
const inRangeForUpdate =
|
||||
updatedRowIndex >= 0 && updatedRowIndex < tableData.length;
|
||||
if (!inRangeForUpdate) return tableData;
|
||||
//if same value ignore update
|
||||
if (tableData[updatedRowIndex][column] === inputValue) return tableData;
|
||||
//create copies of data
|
||||
const copy = [...tableData];
|
||||
copy[updatedRowIndex] = { ...copy[updatedRowIndex], [column]: inputValue };
|
||||
return copy;
|
||||
};
|
||||
|
||||
const getMemoiseInjectEditableCellToTableData = () =>
|
||||
memoizeOne(injectEditableCellToTableData, (prev, next) => {
|
||||
const [prevTableData, prevCellEditable] = prev;
|
||||
const [nextTableData, nextCellEditable] = next;
|
||||
//shallow compare the cellEditable properties
|
||||
if (!shallowEqual(prevCellEditable, nextCellEditable)) return false;
|
||||
|
||||
return shallowEqual(prevTableData, nextTableData);
|
||||
});
|
||||
|
||||
export type transformDataWithEditableCell = (
|
||||
editableCell: EditableCell | undefined,
|
||||
tableData: Array<Record<string, unknown>>,
|
||||
columns: ReactTableColumnProps[],
|
||||
) => tableData;
|
||||
|
||||
// the result of this cache function is a prop for the useTable hook, this prop needs to memoised as per their docs
|
||||
// we have noticed expensive computation from the useTable if tableData isnt memoised
|
||||
export const getMemoiseTransformDataWithEditableCell =
|
||||
(): transformDataWithEditableCell => {
|
||||
const memoizedTransformData = getMemoizedTransformData();
|
||||
const memoiseInjectEditableCellToTableData =
|
||||
getMemoiseInjectEditableCellToTableData();
|
||||
return memoizeOne((editableCell, tableData, columns) => {
|
||||
const transformedData = memoizedTransformData(tableData, columns);
|
||||
return memoiseInjectEditableCellToTableData(
|
||||
transformedData,
|
||||
editableCell,
|
||||
);
|
||||
});
|
||||
};
|
||||
|
|
@ -14943,9 +14943,10 @@ memfs@^3.2.2:
|
|||
version "5.1.1"
|
||||
resolved "https://registry.npmjs.org/memoize-one/-/memoize-one-5.1.1.tgz"
|
||||
|
||||
memoize-one@^5.2.1:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz"
|
||||
memoize-one@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045"
|
||||
integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==
|
||||
|
||||
memoizerific@^1.11.3:
|
||||
version "1.11.3"
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user