diff --git a/pkg/app/web/.scaffdog/page.md b/pkg/app/web/.scaffdog/page.md
index 3f4a7e8402..c728823446 100644
--- a/pkg/app/web/.scaffdog/page.md
+++ b/pkg/app/web/.scaffdog/page.md
@@ -10,10 +10,10 @@ ignore: []
# `{{ inputs.name }}.tsx`
```tsx
-import { memo, FC, useEffect } from "react";
+import { FC, useEffect } from "react";
import { useParams } from "react-router-dom";
-export const {{ inputs.name | pascal }}Page: FC = memo(() => {
+export const {{ inputs.name | pascal }}Page: FC = () => {
return
hello
;
-});
+};
```
diff --git a/pkg/app/web/src/components/application-form/index.tsx b/pkg/app/web/src/components/application-form/index.tsx
index 28face120d..a7534a74fc 100644
--- a/pkg/app/web/src/components/application-form/index.tsx
+++ b/pkg/app/web/src/components/application-form/index.tsx
@@ -168,6 +168,7 @@ export const ApplicationForm: FC = memo(
handleChange,
isSubmitting,
isValid,
+ dirty,
setFieldValue,
setValues,
onClose,
@@ -332,7 +333,7 @@ export const ApplicationForm: FC = memo(
- {"REFRESH"}
+ {UI_TEXT_REFRESH}
{isLoading && (
)}
@@ -184,4 +192,4 @@ export const ApplicationIndexPage: FC = memo(function ApplicationIndexPage() {
>
);
-});
+};
diff --git a/pkg/app/web/src/components/deployments-detail-page/deployment-detail/index.tsx b/pkg/app/web/src/components/deployments-detail-page/deployment-detail/index.tsx
index 11026bec5a..da6c40d170 100644
--- a/pkg/app/web/src/components/deployments-detail-page/deployment-detail/index.tsx
+++ b/pkg/app/web/src/components/deployments-detail-page/deployment-detail/index.tsx
@@ -18,7 +18,6 @@ import { DEPLOYMENT_STATE_TEXT } from "~/constants/deployment-status-text";
import { PAGE_PATH_APPLICATIONS } from "~/constants/path";
import { useAppDispatch, useAppSelector } from "~/hooks/redux";
import { useInterval } from "~/hooks/use-interval";
-import { ActiveStage } from "~/modules/active-stage";
import {
cancelDeployment,
Deployment,
@@ -82,13 +81,10 @@ export const DeploymentDetail: FC = memo(
const classes = useStyles();
const dispatch = useAppDispatch();
- const [deployment, activeStage] = useAppSelector<
- [Deployment.AsObject | undefined, ActiveStage | null]
- >((state) => [
- selectDeploymentById(state.deployments, deploymentId),
- state.activeStage,
- ]);
-
+ const deployment = useAppSelector(
+ (state) => selectDeploymentById(state.deployments, deploymentId)
+ );
+ const activeStage = useAppSelector((state) => state.activeStage);
const env = useAppSelector(selectEnvById(deployment?.envId));
const piped = useAppSelector(selectPipedById(deployment?.pipedId));
const isCanceling = useAppSelector(
diff --git a/pkg/app/web/src/components/deployments-detail-page/log-viewer/index.tsx b/pkg/app/web/src/components/deployments-detail-page/log-viewer/index.tsx
index 7c8c786776..741770a06c 100644
--- a/pkg/app/web/src/components/deployments-detail-page/log-viewer/index.tsx
+++ b/pkg/app/web/src/components/deployments-detail-page/log-viewer/index.tsx
@@ -10,7 +10,7 @@ import clsx from "clsx";
import { FC, memo, useCallback, useState } from "react";
import Draggable from "react-draggable";
import { APP_HEADER_HEIGHT } from "~/components/header";
-import { useAppDispatch, useAppSelector } from "~/hooks/redux";
+import { useAppDispatch, useShallowEqualSelector } from "~/hooks/redux";
import { clearActiveStage } from "~/modules/active-stage";
import { isStageRunning, selectById, Stage } from "~/modules/deployments";
import { selectStageLogById, StageLog } from "~/modules/stage-logs";
@@ -20,7 +20,7 @@ const INITIAL_HEIGHT = 400;
const TOOLBAR_HEIGHT = 48;
function useActiveStageLog(): [Stage | null, StageLog | null] {
- return useAppSelector<[Stage | null, StageLog | null]>((state) => {
+ return useShallowEqualSelector<[Stage | null, StageLog | null]>((state) => {
if (!state.activeStage) {
return [null, null];
}
diff --git a/pkg/app/web/src/components/deployments-page/index.tsx b/pkg/app/web/src/components/deployments-page/index.tsx
index ced3c69cde..149d173b24 100644
--- a/pkg/app/web/src/components/deployments-page/index.tsx
+++ b/pkg/app/web/src/components/deployments-page/index.tsx
@@ -12,7 +12,7 @@ import CloseIcon from "@material-ui/icons/Close";
import FilterIcon from "@material-ui/icons/FilterList";
import RefreshIcon from "@material-ui/icons/Refresh";
import dayjs from "dayjs";
-import { FC, memo, useCallback, useEffect, useRef, useState } from "react";
+import { FC, useCallback, useEffect, useRef, useState } from "react";
import { useInView } from "react-intersection-observer";
import { useHistory } from "react-router-dom";
import { PAGE_PATH_DEPLOYMENTS } from "~/constants/path";
@@ -21,7 +21,11 @@ import {
UI_TEXT_HIDE_FILTER,
UI_TEXT_REFRESH,
} from "~/constants/ui-text";
-import { useAppDispatch, useAppSelector } from "~/hooks/redux";
+import {
+ useAppDispatch,
+ useAppSelector,
+ useShallowEqualSelector,
+} from "~/hooks/redux";
import { fetchApplications } from "~/modules/applications";
import {
Deployment,
@@ -32,7 +36,6 @@ import {
selectIds as selectDeploymentIds,
} from "~/modules/deployments";
import { useStyles as useButtonStyles } from "~/styles/button";
-import { LoadingStatus } from "~/types/module";
import { stringifySearchParams, useSearchParams } from "~/utils/search-params";
import { DeploymentFilter } from "./deployment-filter";
import { DeploymentItem } from "./deployment-item";
@@ -60,20 +63,12 @@ function filterUndefined(value: TValue | undefined): value is TValue {
return value !== undefined;
}
-const useGroupedDeployments = (): [
- LoadingStatus,
- boolean,
- Record
-] => {
- const [status, hasMore, deployments] = useAppSelector<
- [LoadingStatus, boolean, Deployment.AsObject[]]
- >((state) => [
- state.deployments.status,
- state.deployments.hasMore,
+const useGroupedDeployments = (): Record => {
+ const deployments = useShallowEqualSelector((state) =>
selectDeploymentIds(state.deployments)
.map((id) => selectDeploymentById(state.deployments, id))
- .filter(filterUndefined),
- ]);
+ .filter(filterUndefined)
+ );
const result: Record = {};
@@ -85,16 +80,18 @@ const useGroupedDeployments = (): [
result[dateStr].push(deployment);
});
- return [status, hasMore, result];
+ return result;
};
-export const DeploymentIndexPage: FC = memo(function DeploymentIndexPage() {
+export const DeploymentIndexPage: FC = () => {
const classes = useStyles();
const buttonClasses = useButtonStyles();
const history = useHistory();
const dispatch = useAppDispatch();
const listRef = useRef(null);
- const [status, hasMore, groupedDeployments] = useGroupedDeployments();
+ const status = useAppSelector((state) => state.deployments.status);
+ const hasMore = useAppSelector((state) => state.deployments.hasMore);
+ const groupedDeployments = useGroupedDeployments();
const filterOptions = useSearchParams();
const [openFilter, setOpenFilter] = useState(true);
const [ref, inView] = useInView({
@@ -203,4 +200,4 @@ export const DeploymentIndexPage: FC = memo(function DeploymentIndexPage() {
);
-});
+};
diff --git a/pkg/app/web/src/components/header/index.tsx b/pkg/app/web/src/components/header/index.tsx
index 0d04fa6b43..45e228acc3 100644
--- a/pkg/app/web/src/components/header/index.tsx
+++ b/pkg/app/web/src/components/header/index.tsx
@@ -21,9 +21,9 @@ import {
} from "~/constants/path";
import { APP_NAME } from "~/constants/common";
import { NavLink as RouterLink } from "react-router-dom";
-import { useMe } from "~/modules/me";
import ArrowDownIcon from "@material-ui/icons/ArrowDropDown";
import logo from "~~/assets/logo.svg";
+import { useAppSelector } from "~/hooks/redux";
export const APP_HEADER_HEIGHT = 56;
@@ -76,7 +76,7 @@ const useStyles = makeStyles((theme) => ({
export const Header: FC = memo(function Header() {
const classes = useStyles();
- const me = useMe();
+ const me = useAppSelector((state) => state.me);
const [anchorEl, setAnchorEl] = useState(null);
const handleClose = (): void => {
diff --git a/pkg/app/web/src/components/login-page/index.tsx b/pkg/app/web/src/components/login-page/index.tsx
index d6d787c579..fad4b255f4 100644
--- a/pkg/app/web/src/components/login-page/index.tsx
+++ b/pkg/app/web/src/components/login-page/index.tsx
@@ -12,7 +12,7 @@ import { FC, memo, useState } from "react";
import { useCookies } from "react-cookie";
import { Link, Redirect, useHistory, useParams } from "react-router-dom";
import { PAGE_PATH_APPLICATIONS, PAGE_PATH_LOGIN } from "~/constants/path";
-import { useMe } from "~/modules/me";
+import { useAppSelector } from "~/hooks/redux";
import { LoginForm } from "./login-form";
const CONTENT_WIDTH = 500;
@@ -51,7 +51,7 @@ const useStyles = makeStyles((theme) => ({
export const LoginPage: FC = memo(function LoginPage() {
const classes = useStyles();
- const me = useMe();
+ const me = useAppSelector((state) => state.me);
const [name, setName] = useState("");
const [cookies, , removeCookie] = useCookies(["error"]);
const { projectName } = useParams<{ projectName?: string }>();
diff --git a/pkg/app/web/src/hooks/redux.ts b/pkg/app/web/src/hooks/redux.ts
index 8b48feb1c7..1e8a64ad8a 100644
--- a/pkg/app/web/src/hooks/redux.ts
+++ b/pkg/app/web/src/hooks/redux.ts
@@ -1,7 +1,17 @@
// @see https://redux-toolkit.js.org/tutorials/typescript#define-typed-hooks
-import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
+import {
+ shallowEqual,
+ TypedUseSelectorHook,
+ useDispatch,
+ useSelector,
+} from "react-redux";
import type { AppDispatch, AppState } from "~/store";
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type,@typescript-eslint/explicit-module-boundary-types
export const useAppDispatch = () => useDispatch();
export const useAppSelector: TypedUseSelectorHook = useSelector;
+export const useShallowEqualSelector: TypedUseSelectorHook = (
+ selector
+) => {
+ return useSelector(selector, shallowEqual);
+};
diff --git a/pkg/app/web/src/modules/api-keys/index.test.ts b/pkg/app/web/src/modules/api-keys/index.test.ts
index 1b7a3717e7..0798c2e568 100644
--- a/pkg/app/web/src/modules/api-keys/index.test.ts
+++ b/pkg/app/web/src/modules/api-keys/index.test.ts
@@ -6,7 +6,7 @@ import {
generateAPIKey,
fetchAPIKeys,
clearGeneratedKey,
-} from "./";
+} from ".";
const baseState = {
error: null,
diff --git a/pkg/app/web/src/modules/me/index.ts b/pkg/app/web/src/modules/me/index.ts
index 9961b85744..5ede0cb2ad 100644
--- a/pkg/app/web/src/modules/me/index.ts
+++ b/pkg/app/web/src/modules/me/index.ts
@@ -1,7 +1,6 @@
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import { Role } from "pipe/pkg/app/web/model/role_pb";
import { getMe } from "~/api/me";
-import { useAppSelector } from "~/hooks/redux";
interface Me {
subject: string;
@@ -41,7 +40,4 @@ export const selectProjectName = (state: { me: MeState }): string => {
return "";
};
-export const useMe = (): MeState =>
- useAppSelector((state) => state.me);
-
export { Role } from "pipe/pkg/app/web/model/role_pb";
diff --git a/pkg/app/web/src/routes.tsx b/pkg/app/web/src/routes.tsx
index 521cd67776..881f7f528b 100644
--- a/pkg/app/web/src/routes.tsx
+++ b/pkg/app/web/src/routes.tsx
@@ -1,6 +1,6 @@
import loadable from "@loadable/component";
import { EntityId } from "@reduxjs/toolkit";
-import { FC, memo, useEffect } from "react";
+import { FC, useEffect } from "react";
import { Redirect, Route, Switch } from "react-router-dom";
import { ApplicationIndexPage } from "~/components/applications-page";
import { DeploymentIndexPage } from "~/components/deployments-page";
@@ -22,7 +22,6 @@ import {
selectIds as selectCommandIds,
} from "~/modules/commands";
import { fetchEnvironments } from "~/modules/environments";
-import { useMe } from "~/modules/me";
import { fetchPipeds } from "~/modules/pipeds";
const SettingsIndexPage = loadable(
@@ -79,9 +78,9 @@ const useCommandsStatusChecking = (): void => {
);
};
-export const Routes: FC = memo(function Routes() {
+export const Routes: FC = () => {
const dispatch = useAppDispatch();
- const me = useMe();
+ const me = useAppSelector((state) => state.me);
useEffect(() => {
if (me?.isLogin) {
dispatch(fetchEnvironments());
@@ -154,4 +153,4 @@ export const Routes: FC = memo(function Routes() {
>
);
-});
+};