Fix unnecessary renders of ActionCreator, EntityName, CodeEditor and ActionEntityContextMenu (#6242)
* Fix action creator unnecessary render issue - Optimize the selectors and hooks to return new values only when something has changed. * Fix ActionEntityContextMenu re-renders issue * Prevent rerenders of EntityName component * Fix CodeEditor re-renders * Use createSelector instead of memoization. * Cleanup * - Remove whyDidYouRender Co-authored-by: Satish Gandham <satish@appsmith.com>
This commit is contained in:
parent
ce6f42683e
commit
88c92fd2f5
|
|
@ -1,40 +1,41 @@
|
|||
import React, { useMemo } from "react";
|
||||
import { AppState } from "reducers";
|
||||
import {
|
||||
getActionsForCurrentPage,
|
||||
getDBDatasources,
|
||||
} from "selectors/entitiesSelector";
|
||||
import { createActionRequest } from "actions/actionActions";
|
||||
import {
|
||||
getModalDropdownList,
|
||||
getNextModalName,
|
||||
} from "selectors/widgetSelectors";
|
||||
import { createModalAction } from "actions/widgetActions";
|
||||
import { TreeDropdownOption } from "components/ads/TreeDropdown";
|
||||
import TreeStructure from "components/utils/TreeStructure";
|
||||
import { OnboardingStep } from "constants/OnboardingConstants";
|
||||
import { ReduxActionTypes } from "constants/ReduxActionConstants";
|
||||
import { INTEGRATION_EDITOR_URL, INTEGRATION_TABS } from "constants/routes";
|
||||
import { PluginType } from "entities/Action";
|
||||
import { Datasource } from "entities/Datasource";
|
||||
import { keyBy } from "lodash";
|
||||
import { getActionConfig } from "pages/Editor/Explorer/Actions/helpers";
|
||||
import { apiIcon, getPluginIcon } from "pages/Editor/Explorer/ExplorerIcons";
|
||||
import React, { useMemo } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { AppState } from "reducers";
|
||||
import { getCurrentStep, getCurrentSubStep } from "sagas/OnboardingSagas";
|
||||
import { getWidgetOptionsTree } from "sagas/selectors";
|
||||
import {
|
||||
getCurrentApplicationId,
|
||||
getCurrentPageId,
|
||||
} from "selectors/editorSelectors";
|
||||
import { Datasource } from "entities/Datasource";
|
||||
import {
|
||||
getActionsForCurrentPage,
|
||||
getDBDatasources,
|
||||
getPageListAsOptions,
|
||||
} from "selectors/entitiesSelector";
|
||||
import {
|
||||
getModalDropdownList,
|
||||
getNextModalName,
|
||||
} from "selectors/widgetSelectors";
|
||||
import { createNewQueryName } from "utils/AppsmithUtils";
|
||||
import history from "utils/history";
|
||||
import Fields, {
|
||||
ACTION_TRIGGER_REGEX,
|
||||
ACTION_ANONYMOUS_FUNC_REGEX,
|
||||
ActionType,
|
||||
ACTION_ANONYMOUS_FUNC_REGEX,
|
||||
ACTION_TRIGGER_REGEX,
|
||||
FieldType,
|
||||
} from "./Fields";
|
||||
import { TreeDropdownOption } from "components/ads/TreeDropdown";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { createModalAction } from "actions/widgetActions";
|
||||
import { createNewQueryName } from "utils/AppsmithUtils";
|
||||
import TreeStructure from "components/utils/TreeStructure";
|
||||
import { getWidgets } from "sagas/selectors";
|
||||
import { PluginType } from "entities/Action";
|
||||
import { INTEGRATION_EDITOR_URL, INTEGRATION_TABS } from "constants/routes";
|
||||
import history from "utils/history";
|
||||
import { keyBy } from "lodash";
|
||||
import { getPluginIcon, apiIcon } from "pages/Editor/Explorer/ExplorerIcons";
|
||||
import { getActionConfig } from "pages/Editor/Explorer/Actions/helpers";
|
||||
import { getCurrentStep, getCurrentSubStep } from "sagas/OnboardingSagas";
|
||||
import { OnboardingStep } from "constants/OnboardingConstants";
|
||||
import { ReduxActionTypes } from "constants/ReduxActionConstants";
|
||||
|
||||
/* eslint-disable @typescript-eslint/ban-types */
|
||||
/* TODO: Function and object types need to be updated to enable the lint rule */
|
||||
|
|
@ -226,14 +227,6 @@ function getFieldFromValue(
|
|||
return fields;
|
||||
}
|
||||
|
||||
function getPageDropdownOptions(state: AppState) {
|
||||
return state.entities.pageList.pages.map((page) => ({
|
||||
label: page.pageName,
|
||||
id: page.pageId,
|
||||
value: `'${page.pageName}'`,
|
||||
}));
|
||||
}
|
||||
|
||||
function useModalDropdownList() {
|
||||
const dispatch = useDispatch();
|
||||
const nextModalName = useSelector(getNextModalName);
|
||||
|
|
@ -264,19 +257,6 @@ function useModalDropdownList() {
|
|||
return finalList;
|
||||
}
|
||||
|
||||
function useWidgetOptionTree() {
|
||||
const widgets = useSelector(getWidgets) || {};
|
||||
return Object.values(widgets)
|
||||
.filter((w) => w.type !== "CANVAS_WIDGET" && w.type !== "BUTTON_WIDGET")
|
||||
.map((w) => {
|
||||
return {
|
||||
label: w.widgetName,
|
||||
id: w.widgetName,
|
||||
value: `"${w.widgetName}"`,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function getIntegrationOptionsWithChildren(
|
||||
pageId: string,
|
||||
plugins: any,
|
||||
|
|
@ -412,9 +392,9 @@ type ActionCreatorProps = {
|
|||
|
||||
export function ActionCreator(props: ActionCreatorProps) {
|
||||
const integrationOptionTree = useIntegrationsOptionTree();
|
||||
const widgetOptionTree = useWidgetOptionTree();
|
||||
const widgetOptionTree = useSelector(getWidgetOptionsTree);
|
||||
const modalDropdownList = useModalDropdownList();
|
||||
const pageDropdownOptions = useSelector(getPageDropdownOptions);
|
||||
const pageDropdownOptions = useSelector(getPageListAsOptions);
|
||||
const fields = getFieldFromValue(props.value);
|
||||
return (
|
||||
<TreeStructure>
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ import { getEntityNameAndPropertyPath } from "workers/evaluationUtils";
|
|||
import Button from "components/ads/Button";
|
||||
import { getPluginIdToImageLocation } from "sagas/selectors";
|
||||
import { ExpectedValueExample } from "utils/validation/common";
|
||||
import { getRecentEntityIds } from "selectors/globalSearchSelectors";
|
||||
|
||||
const AUTOCOMPLETE_CLOSE_KEY_CODES = [
|
||||
"Enter",
|
||||
|
|
@ -600,7 +601,7 @@ const mapStateToProps = (state: AppState): ReduxStateProps => ({
|
|||
dynamicData: getDataTreeForAutocomplete(state),
|
||||
datasources: state.entities.datasources,
|
||||
pluginIdToImageLocation: getPluginIdToImageLocation(state),
|
||||
recentEntities: state.ui.globalSearch.recentEntities.map((r) => r.id),
|
||||
recentEntities: getRecentEntityIds(state),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch: any): ReduxDispatchProps => ({
|
||||
|
|
|
|||
|
|
@ -1,23 +1,20 @@
|
|||
import React, { useCallback } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import TreeDropdown from "pages/Editor/Explorer/TreeDropdown";
|
||||
|
||||
import { AppState } from "reducers";
|
||||
import ContextMenuTrigger from "../ContextMenuTrigger";
|
||||
|
||||
import {
|
||||
moveActionRequest,
|
||||
copyActionRequest,
|
||||
deleteAction,
|
||||
moveActionRequest,
|
||||
} from "actions/actionActions";
|
||||
|
||||
import { initExplorerEntityNameEdit } from "actions/explorerActions";
|
||||
import { ContextMenuPopoverModifiers, ExplorerURLParams } from "../helpers";
|
||||
import { noop } from "lodash";
|
||||
import { useNewActionName } from "./helpers";
|
||||
import { useParams } from "react-router";
|
||||
import { BUILDER_PAGE_URL } from "constants/routes";
|
||||
import { noop } from "lodash";
|
||||
import TreeDropdown from "pages/Editor/Explorer/TreeDropdown";
|
||||
import React, { useCallback } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useParams } from "react-router";
|
||||
import { getPageListAsOptions } from "selectors/entitiesSelector";
|
||||
import history from "utils/history";
|
||||
import ContextMenuTrigger from "../ContextMenuTrigger";
|
||||
import { ContextMenuPopoverModifiers, ExplorerURLParams } from "../helpers";
|
||||
import { useNewActionName } from "./helpers";
|
||||
|
||||
type EntityContextMenuProps = {
|
||||
id: string;
|
||||
|
|
@ -58,13 +55,7 @@ export function ActionEntityContextMenu(props: EntityContextMenuProps) {
|
|||
[dispatch],
|
||||
);
|
||||
|
||||
const menuPages = useSelector((state: AppState) => {
|
||||
return state.entities.pageList.pages.map((page) => ({
|
||||
label: page.pageName,
|
||||
id: page.pageId,
|
||||
value: page.pageName,
|
||||
}));
|
||||
});
|
||||
const menuPages = useSelector(getPageListAsOptions);
|
||||
|
||||
const editActionName = useCallback(
|
||||
() => dispatch(initExplorerEntityNameEdit(props.id)),
|
||||
|
|
|
|||
|
|
@ -1,20 +1,25 @@
|
|||
import React, {
|
||||
useCallback,
|
||||
useMemo,
|
||||
useState,
|
||||
useEffect,
|
||||
forwardRef,
|
||||
} from "react";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import styled from "styled-components";
|
||||
import EditableText, {
|
||||
EditInteractionKind,
|
||||
} from "components/editorComponents/EditableText";
|
||||
import { removeSpecialChars } from "utils/helpers";
|
||||
import { AppState } from "reducers";
|
||||
import { Page, ReduxActionTypes } from "constants/ReduxActionConstants";
|
||||
import { Colors } from "constants/Colors";
|
||||
import { ReduxActionTypes } from "constants/ReduxActionConstants";
|
||||
import { WidgetTypes } from "constants/WidgetConstants";
|
||||
import React, {
|
||||
forwardRef,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { AppState } from "reducers";
|
||||
import {
|
||||
getExistingActionNames,
|
||||
getExistingPageNames,
|
||||
getExistingWidgetNames,
|
||||
} from "selectors/entitiesSelector";
|
||||
import styled from "styled-components";
|
||||
import { removeSpecialChars } from "utils/helpers";
|
||||
|
||||
const searchHighlightSpanClassName = "token";
|
||||
const searchTokenizationDelimiter = "!!";
|
||||
|
|
@ -100,22 +105,12 @@ export const EntityName = forwardRef(
|
|||
setUpdatedName(name);
|
||||
}, [name, nameUpdateError]);
|
||||
|
||||
const existingPageNames: string[] = useSelector((state: AppState) =>
|
||||
state.entities.pageList.pages.map((page: Page) => page.pageName),
|
||||
);
|
||||
const existingPageNames: string[] = useSelector(getExistingPageNames);
|
||||
const existingWidgetNames: string[] = useSelector(getExistingWidgetNames);
|
||||
|
||||
const existingWidgetNames: string[] = useSelector((state: AppState) =>
|
||||
Object.values(state.entities.canvasWidgets).map(
|
||||
(widget) => widget.widgetName,
|
||||
),
|
||||
);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const existingActionNames: string[] = useSelector((state: AppState) =>
|
||||
state.entities.actions.map(
|
||||
(action: { config: { name: string } }) => action.config.name,
|
||||
),
|
||||
);
|
||||
const existingActionNames: string[] = useSelector(getExistingActionNames);
|
||||
|
||||
const hasNameConflict = useCallback(
|
||||
(
|
||||
|
|
|
|||
|
|
@ -29,6 +29,18 @@ export const getWidgetIdsByType = (state: AppState, type: WidgetType) => {
|
|||
.map((widget: FlattenedWidgetProps) => widget.widgetId);
|
||||
};
|
||||
|
||||
export const getWidgetOptionsTree = createSelector(getWidgets, (widgets) =>
|
||||
Object.values(widgets)
|
||||
.filter((w) => w.type !== "CANVAS_WIDGET" && w.type !== "BUTTON_WIDGET")
|
||||
.map((w) => {
|
||||
return {
|
||||
label: w.widgetName,
|
||||
id: w.widgetName,
|
||||
value: `"${w.widgetName}"`,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
export const getEditorConfigs = (
|
||||
state: AppState,
|
||||
): { pageId: string; layoutId: string } | undefined => {
|
||||
|
|
|
|||
|
|
@ -403,4 +403,30 @@ export const getAllPageWidgets = createSelector(
|
|||
},
|
||||
);
|
||||
|
||||
export const getPageListAsOptions = createSelector(
|
||||
(state: AppState) => state.entities.pageList.pages,
|
||||
(pages) =>
|
||||
pages.map((page) => ({
|
||||
label: page.pageName,
|
||||
id: page.pageId,
|
||||
value: `'${page.pageName}'`,
|
||||
})),
|
||||
);
|
||||
|
||||
export const getExistingPageNames = createSelector(
|
||||
(state: AppState) => state.entities.pageList.pages,
|
||||
(pages) => pages.map((page) => page.pageName),
|
||||
);
|
||||
|
||||
export const getExistingWidgetNames = createSelector(
|
||||
(state: AppState) => state.entities.canvasWidgets,
|
||||
(widgets) => Object.values(widgets).map((widget) => widget.pageName),
|
||||
);
|
||||
|
||||
export const getExistingActionNames = createSelector(
|
||||
(state: AppState) => state.entities.actions,
|
||||
(actions) =>
|
||||
actions.map((action: { config: { name: string } }) => action.config.name),
|
||||
);
|
||||
|
||||
export const getAppMode = (state: AppState) => state.entities.app.mode;
|
||||
|
|
|
|||
14
app/client/src/selectors/globalSearchSelectors.tsx
Normal file
14
app/client/src/selectors/globalSearchSelectors.tsx
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { createSelector } from "reselect";
|
||||
|
||||
import { AppState } from "reducers";
|
||||
import { RecentEntity } from "components/editorComponents/GlobalSearch/utils";
|
||||
|
||||
export const getRecentEntities = (state: AppState) =>
|
||||
state.ui.globalSearch.recentEntities;
|
||||
|
||||
export const getRecentEntityIds = createSelector(
|
||||
getRecentEntities,
|
||||
(entities: RecentEntity[]) => {
|
||||
return entities.map((r) => r.id);
|
||||
},
|
||||
);
|
||||
Loading…
Reference in New Issue
Block a user