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:
Satish Gandham 2021-08-02 21:36:33 +05:30 committed by GitHub
parent ce6f42683e
commit 88c92fd2f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 116 additions and 97 deletions

View File

@ -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>

View File

@ -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 => ({

View File

@ -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)),

View File

@ -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(
(

View File

@ -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 => {

View File

@ -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;

View 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);
},
);